| /* |
| * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.sun.tools.javac.file; |
| |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.zip.DataFormatException; |
| import java.util.zip.Inflater; |
| import java.util.zip.ZipException; |
| |
| import com.sun.tools.javac.file.RelativePath.RelativeDirectory; |
| import com.sun.tools.javac.file.RelativePath.RelativeFile; |
| |
| /** |
| * This class implements the building of index of a zip archive and access to |
| * its context. It also uses a prebuilt index if available. |
| * It supports invocations where it will serialize an optimized zip index file |
| * to disk. |
| * |
| * In order to use a secondary index file, set "usezipindex" in the Options |
| * object when JavacFileManager is invoked. (You can pass "-XDusezipindex" on |
| * the command line.) |
| * |
| * Location where to look for/generate optimized zip index files can be |
| * provided using "{@code -XDcachezipindexdir=<directory>}". If this flag is not |
| * provided, the default location is the value of the "java.io.tmpdir" system |
| * property. |
| * |
| * If "-XDwritezipindexfiles" is specified, there will be new optimized index |
| * file created for each archive, used by the compiler for compilation, at the |
| * location specified by the "cachezipindexdir" option. |
| * |
| * If system property nonBatchMode option is specified the compiler will use |
| * timestamp checking to reindex the zip files if it is needed. In batch mode |
| * the timestamps are not checked and the compiler uses the cached indexes. |
| * |
| * <p><b>This is NOT part of any supported API. |
| * If you write code that depends on this, you do so at your own risk. |
| * This code and its internal interfaces are subject to change or |
| * deletion without notice.</b> |
| */ |
| public class ZipFileIndex { |
| private static final String MIN_CHAR = String.valueOf(Character.MIN_VALUE); |
| private static final String MAX_CHAR = String.valueOf(Character.MAX_VALUE); |
| |
| public final static long NOT_MODIFIED = Long.MIN_VALUE; |
| |
| |
| private static final boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this. |
| |
| private Map<RelativeDirectory, DirectoryEntry> directories = |
| Collections.<RelativeDirectory, DirectoryEntry>emptyMap(); |
| private Set<RelativeDirectory> allDirs = |
| Collections.<RelativeDirectory>emptySet(); |
| |
| // ZipFileIndex data entries |
| final File zipFile; |
| private Reference<File> absFileRef; |
| long zipFileLastModified = NOT_MODIFIED; |
| private RandomAccessFile zipRandomFile; |
| private Entry[] entries; |
| |
| private boolean readFromIndex = false; |
| private File zipIndexFile = null; |
| private boolean triedToReadIndex = false; |
| final RelativeDirectory symbolFilePrefix; |
| private final int symbolFilePrefixLength; |
| private boolean hasPopulatedData = false; |
| long lastReferenceTimeStamp = NOT_MODIFIED; |
| |
| private final boolean usePreindexedCache; |
| private final String preindexedCacheLocation; |
| |
| private boolean writeIndex = false; |
| |
| private Map<String, SoftReference<RelativeDirectory>> relativeDirectoryCache = |
| new HashMap<String, SoftReference<RelativeDirectory>>(); |
| |
| |
| public synchronized boolean isOpen() { |
| return (zipRandomFile != null); |
| } |
| |
| ZipFileIndex(File zipFile, RelativeDirectory symbolFilePrefix, boolean writeIndex, |
| boolean useCache, String cacheLocation) throws IOException { |
| this.zipFile = zipFile; |
| this.symbolFilePrefix = symbolFilePrefix; |
| this.symbolFilePrefixLength = (symbolFilePrefix == null ? 0 : |
| symbolFilePrefix.getPath().getBytes("UTF-8").length); |
| this.writeIndex = writeIndex; |
| this.usePreindexedCache = useCache; |
| this.preindexedCacheLocation = cacheLocation; |
| |
| if (zipFile != null) { |
| this.zipFileLastModified = zipFile.lastModified(); |
| } |
| |
| // Validate integrity of the zip file |
| checkIndex(); |
| } |
| |
| @Override |
| public String toString() { |
| return "ZipFileIndex[" + zipFile + "]"; |
| } |
| |
| // Just in case... |
| @Override |
| protected void finalize() throws Throwable { |
| closeFile(); |
| super.finalize(); |
| } |
| |
| private boolean isUpToDate() { |
| if (zipFile != null |
| && ((!NON_BATCH_MODE) || zipFileLastModified == zipFile.lastModified()) |
| && hasPopulatedData) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Here we need to make sure that the ZipFileIndex is valid. Check the timestamp of the file and |
| * if its the same as the one at the time the index was build we don't need to reopen anything. |
| */ |
| private void checkIndex() throws IOException { |
| boolean isUpToDate = true; |
| if (!isUpToDate()) { |
| closeFile(); |
| isUpToDate = false; |
| } |
| |
| if (zipRandomFile != null || isUpToDate) { |
| lastReferenceTimeStamp = System.currentTimeMillis(); |
| return; |
| } |
| |
| hasPopulatedData = true; |
| |
| if (readIndex()) { |
| lastReferenceTimeStamp = System.currentTimeMillis(); |
| return; |
| } |
| |
| directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap(); |
| allDirs = Collections.<RelativeDirectory>emptySet(); |
| |
| try { |
| openFile(); |
| long totalLength = zipRandomFile.length(); |
| ZipDirectory directory = new ZipDirectory(zipRandomFile, 0L, totalLength, this); |
| directory.buildIndex(); |
| } finally { |
| if (zipRandomFile != null) { |
| closeFile(); |
| } |
| } |
| |
| lastReferenceTimeStamp = System.currentTimeMillis(); |
| } |
| |
| private void openFile() throws FileNotFoundException { |
| if (zipRandomFile == null && zipFile != null) { |
| zipRandomFile = new RandomAccessFile(zipFile, "r"); |
| } |
| } |
| |
| private void cleanupState() { |
| // Make sure there is a valid but empty index if the file doesn't exist |
| entries = Entry.EMPTY_ARRAY; |
| directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap(); |
| zipFileLastModified = NOT_MODIFIED; |
| allDirs = Collections.<RelativeDirectory>emptySet(); |
| } |
| |
| public synchronized void close() { |
| writeIndex(); |
| closeFile(); |
| } |
| |
| private void closeFile() { |
| if (zipRandomFile != null) { |
| try { |
| zipRandomFile.close(); |
| } catch (IOException ex) { |
| } |
| zipRandomFile = null; |
| } |
| } |
| |
| /** |
| * Returns the ZipFileIndexEntry for a path, if there is one. |
| */ |
| synchronized Entry getZipIndexEntry(RelativePath path) { |
| try { |
| checkIndex(); |
| DirectoryEntry de = directories.get(path.dirname()); |
| String lookFor = path.basename(); |
| return (de == null) ? null : de.getEntry(lookFor); |
| } |
| catch (IOException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns a javac List of filenames within a directory in the ZipFileIndex. |
| */ |
| public synchronized com.sun.tools.javac.util.List<String> getFiles(RelativeDirectory path) { |
| try { |
| checkIndex(); |
| |
| DirectoryEntry de = directories.get(path); |
| com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getFiles(); |
| |
| if (ret == null) { |
| return com.sun.tools.javac.util.List.<String>nil(); |
| } |
| return ret; |
| } |
| catch (IOException e) { |
| return com.sun.tools.javac.util.List.<String>nil(); |
| } |
| } |
| |
| public synchronized List<String> getDirectories(RelativeDirectory path) { |
| try { |
| checkIndex(); |
| |
| DirectoryEntry de = directories.get(path); |
| com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getDirectories(); |
| |
| if (ret == null) { |
| return com.sun.tools.javac.util.List.<String>nil(); |
| } |
| |
| return ret; |
| } |
| catch (IOException e) { |
| return com.sun.tools.javac.util.List.<String>nil(); |
| } |
| } |
| |
| public synchronized Set<RelativeDirectory> getAllDirectories() { |
| try { |
| checkIndex(); |
| if (allDirs == Collections.EMPTY_SET) { |
| allDirs = new java.util.LinkedHashSet<RelativeDirectory>(directories.keySet()); |
| } |
| |
| return allDirs; |
| } |
| catch (IOException e) { |
| return Collections.<RelativeDirectory>emptySet(); |
| } |
| } |
| |
| /** |
| * Tests if a specific path exists in the zip. This method will return true |
| * for file entries and directories. |
| * |
| * @param path A path within the zip. |
| * @return True if the path is a file or dir, false otherwise. |
| */ |
| public synchronized boolean contains(RelativePath path) { |
| try { |
| checkIndex(); |
| return getZipIndexEntry(path) != null; |
| } |
| catch (IOException e) { |
| return false; |
| } |
| } |
| |
| public synchronized boolean isDirectory(RelativePath path) throws IOException { |
| // The top level in a zip file is always a directory. |
| if (path.getPath().length() == 0) { |
| lastReferenceTimeStamp = System.currentTimeMillis(); |
| return true; |
| } |
| |
| checkIndex(); |
| return directories.get(path) != null; |
| } |
| |
| public synchronized long getLastModified(RelativeFile path) throws IOException { |
| Entry entry = getZipIndexEntry(path); |
| if (entry == null) |
| throw new FileNotFoundException(); |
| return entry.getLastModified(); |
| } |
| |
| public synchronized int length(RelativeFile path) throws IOException { |
| Entry entry = getZipIndexEntry(path); |
| if (entry == null) |
| throw new FileNotFoundException(); |
| |
| if (entry.isDir) { |
| return 0; |
| } |
| |
| byte[] header = getHeader(entry); |
| // entry is not compressed? |
| if (get2ByteLittleEndian(header, 8) == 0) { |
| return entry.compressedSize; |
| } else { |
| return entry.size; |
| } |
| } |
| |
| public synchronized byte[] read(RelativeFile path) throws IOException { |
| Entry entry = getZipIndexEntry(path); |
| if (entry == null) |
| throw new FileNotFoundException("Path not found in ZIP: " + path.path); |
| return read(entry); |
| } |
| |
| synchronized byte[] read(Entry entry) throws IOException { |
| openFile(); |
| byte[] result = readBytes(entry); |
| closeFile(); |
| return result; |
| } |
| |
| public synchronized int read(RelativeFile path, byte[] buffer) throws IOException { |
| Entry entry = getZipIndexEntry(path); |
| if (entry == null) |
| throw new FileNotFoundException(); |
| return read(entry, buffer); |
| } |
| |
| synchronized int read(Entry entry, byte[] buffer) |
| throws IOException { |
| int result = readBytes(entry, buffer); |
| return result; |
| } |
| |
| private byte[] readBytes(Entry entry) throws IOException { |
| byte[] header = getHeader(entry); |
| int csize = entry.compressedSize; |
| byte[] cbuf = new byte[csize]; |
| zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); |
| zipRandomFile.readFully(cbuf, 0, csize); |
| |
| // is this compressed - offset 8 in the ZipEntry header |
| if (get2ByteLittleEndian(header, 8) == 0) |
| return cbuf; |
| |
| int size = entry.size; |
| byte[] buf = new byte[size]; |
| if (inflate(cbuf, buf) != size) |
| throw new ZipException("corrupted zip file"); |
| |
| return buf; |
| } |
| |
| /** |
| * |
| */ |
| private int readBytes(Entry entry, byte[] buffer) throws IOException { |
| byte[] header = getHeader(entry); |
| |
| // entry is not compressed? |
| if (get2ByteLittleEndian(header, 8) == 0) { |
| zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); |
| int offset = 0; |
| int size = buffer.length; |
| while (offset < size) { |
| int count = zipRandomFile.read(buffer, offset, size - offset); |
| if (count == -1) |
| break; |
| offset += count; |
| } |
| return entry.size; |
| } |
| |
| int csize = entry.compressedSize; |
| byte[] cbuf = new byte[csize]; |
| zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); |
| zipRandomFile.readFully(cbuf, 0, csize); |
| |
| int count = inflate(cbuf, buffer); |
| if (count == -1) |
| throw new ZipException("corrupted zip file"); |
| |
| return entry.size; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Zip utilities |
| //---------------------------------------------------------------------------- |
| |
| private byte[] getHeader(Entry entry) throws IOException { |
| zipRandomFile.seek(entry.offset); |
| byte[] header = new byte[30]; |
| zipRandomFile.readFully(header); |
| if (get4ByteLittleEndian(header, 0) != 0x04034b50) |
| throw new ZipException("corrupted zip file"); |
| if ((get2ByteLittleEndian(header, 6) & 1) != 0) |
| throw new ZipException("encrypted zip file"); // offset 6 in the header of the ZipFileEntry |
| return header; |
| } |
| |
| /* |
| * Inflate using the java.util.zip.Inflater class |
| */ |
| private SoftReference<Inflater> inflaterRef; |
| private int inflate(byte[] src, byte[] dest) { |
| Inflater inflater = (inflaterRef == null ? null : inflaterRef.get()); |
| |
| // construct the inflater object or reuse an existing one |
| if (inflater == null) |
| inflaterRef = new SoftReference<Inflater>(inflater = new Inflater(true)); |
| |
| inflater.reset(); |
| inflater.setInput(src); |
| try { |
| return inflater.inflate(dest); |
| } catch (DataFormatException ex) { |
| return -1; |
| } |
| } |
| |
| /** |
| * return the two bytes buf[pos], buf[pos+1] as an unsigned integer in little |
| * endian format. |
| */ |
| private static int get2ByteLittleEndian(byte[] buf, int pos) { |
| return (buf[pos] & 0xFF) + ((buf[pos+1] & 0xFF) << 8); |
| } |
| |
| /** |
| * return the 4 bytes buf[i..i+3] as an integer in little endian format. |
| */ |
| private static int get4ByteLittleEndian(byte[] buf, int pos) { |
| return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8) + |
| ((buf[pos + 2] & 0xFF) << 16) + ((buf[pos + 3] & 0xFF) << 24); |
| } |
| |
| /* ---------------------------------------------------------------------------- |
| * ZipDirectory |
| * ----------------------------------------------------------------------------*/ |
| |
| private class ZipDirectory { |
| private RelativeDirectory lastDir; |
| private int lastStart; |
| private int lastLen; |
| |
| byte[] zipDir; |
| RandomAccessFile zipRandomFile = null; |
| ZipFileIndex zipFileIndex = null; |
| |
| public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException { |
| this.zipRandomFile = zipRandomFile; |
| this.zipFileIndex = index; |
| hasValidHeader(); |
| findCENRecord(start, end); |
| } |
| |
| /* |
| * the zip entry signature should be at offset 0, otherwise allow the |
| * calling logic to take evasive action by throwing ZipFormatException. |
| */ |
| private boolean hasValidHeader() throws IOException { |
| final long pos = zipRandomFile.getFilePointer(); |
| try { |
| if (zipRandomFile.read() == 'P') { |
| if (zipRandomFile.read() == 'K') { |
| if (zipRandomFile.read() == 0x03) { |
| if (zipRandomFile.read() == 0x04) { |
| return true; |
| } |
| } |
| } |
| } |
| } finally { |
| zipRandomFile.seek(pos); |
| } |
| throw new ZipFormatException("invalid zip magic"); |
| } |
| |
| /* |
| * Reads zip file central directory. |
| * For more details see readCEN in zip_util.c from the JDK sources. |
| * This is a Java port of that function. |
| */ |
| private void findCENRecord(long start, long end) throws IOException { |
| long totalLength = end - start; |
| int endbuflen = 1024; |
| byte[] endbuf = new byte[endbuflen]; |
| long endbufend = end - start; |
| |
| // There is a variable-length field after the dir offset record. We need to do consequential search. |
| while (endbufend >= 22) { |
| if (endbufend < endbuflen) |
| endbuflen = (int)endbufend; |
| long endbufpos = endbufend - endbuflen; |
| zipRandomFile.seek(start + endbufpos); |
| zipRandomFile.readFully(endbuf, 0, endbuflen); |
| int i = endbuflen - 22; |
| while (i >= 0 && |
| !(endbuf[i] == 0x50 && |
| endbuf[i + 1] == 0x4b && |
| endbuf[i + 2] == 0x05 && |
| endbuf[i + 3] == 0x06 && |
| endbufpos + i + 22 + |
| get2ByteLittleEndian(endbuf, i + 20) == totalLength)) { |
| i--; |
| } |
| |
| if (i >= 0) { |
| zipDir = new byte[get4ByteLittleEndian(endbuf, i + 12)]; |
| int sz = get4ByteLittleEndian(endbuf, i + 16); |
| // a negative offset or the entries field indicates a |
| // potential zip64 archive |
| if (sz < 0 || get2ByteLittleEndian(endbuf, i + 10) == 0xffff) { |
| throw new ZipFormatException("detected a zip64 archive"); |
| } |
| zipRandomFile.seek(start + sz); |
| zipRandomFile.readFully(zipDir, 0, zipDir.length); |
| return; |
| } else { |
| endbufend = endbufpos + 21; |
| } |
| } |
| throw new ZipException("cannot read zip file"); |
| } |
| |
| private void buildIndex() throws IOException { |
| int len = zipDir.length; |
| |
| // Add each of the files |
| if (len > 0) { |
| directories = new LinkedHashMap<RelativeDirectory, DirectoryEntry>(); |
| ArrayList<Entry> entryList = new ArrayList<Entry>(); |
| for (int pos = 0; pos < len; ) { |
| pos = readEntry(pos, entryList, directories); |
| } |
| |
| // Add the accumulated dirs into the same list |
| for (RelativeDirectory d: directories.keySet()) { |
| // use shared RelativeDirectory objects for parent dirs |
| RelativeDirectory parent = getRelativeDirectory(d.dirname().getPath()); |
| String file = d.basename(); |
| Entry zipFileIndexEntry = new Entry(parent, file); |
| zipFileIndexEntry.isDir = true; |
| entryList.add(zipFileIndexEntry); |
| } |
| |
| entries = entryList.toArray(new Entry[entryList.size()]); |
| Arrays.sort(entries); |
| } else { |
| cleanupState(); |
| } |
| } |
| |
| private int readEntry(int pos, List<Entry> entryList, |
| Map<RelativeDirectory, DirectoryEntry> directories) throws IOException { |
| if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) { |
| throw new ZipException("cannot read zip file entry"); |
| } |
| |
| int dirStart = pos + 46; |
| int fileStart = dirStart; |
| int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28); |
| |
| if (zipFileIndex.symbolFilePrefixLength != 0 && |
| ((fileEnd - fileStart) >= symbolFilePrefixLength)) { |
| dirStart += zipFileIndex.symbolFilePrefixLength; |
| fileStart += zipFileIndex.symbolFilePrefixLength; |
| } |
| // Force any '\' to '/'. Keep the position of the last separator. |
| for (int index = fileStart; index < fileEnd; index++) { |
| byte nextByte = zipDir[index]; |
| if (nextByte == (byte)'\\') { |
| zipDir[index] = (byte)'/'; |
| fileStart = index + 1; |
| } else if (nextByte == (byte)'/') { |
| fileStart = index + 1; |
| } |
| } |
| |
| RelativeDirectory directory = null; |
| if (fileStart == dirStart) |
| directory = getRelativeDirectory(""); |
| else if (lastDir != null && lastLen == fileStart - dirStart - 1) { |
| int index = lastLen - 1; |
| while (zipDir[lastStart + index] == zipDir[dirStart + index]) { |
| if (index == 0) { |
| directory = lastDir; |
| break; |
| } |
| index--; |
| } |
| } |
| |
| // Sub directories |
| if (directory == null) { |
| lastStart = dirStart; |
| lastLen = fileStart - dirStart - 1; |
| |
| directory = getRelativeDirectory(new String(zipDir, dirStart, lastLen, "UTF-8")); |
| lastDir = directory; |
| |
| // Enter also all the parent directories |
| RelativeDirectory tempDirectory = directory; |
| |
| while (directories.get(tempDirectory) == null) { |
| directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex)); |
| if (tempDirectory.path.indexOf("/") == tempDirectory.path.length() - 1) |
| break; |
| else { |
| // use shared RelativeDirectory objects for parent dirs |
| tempDirectory = getRelativeDirectory(tempDirectory.dirname().getPath()); |
| } |
| } |
| } |
| else { |
| if (directories.get(directory) == null) { |
| directories.put(directory, new DirectoryEntry(directory, zipFileIndex)); |
| } |
| } |
| |
| // For each dir create also a file |
| if (fileStart != fileEnd) { |
| Entry entry = new Entry(directory, |
| new String(zipDir, fileStart, fileEnd - fileStart, "UTF-8")); |
| |
| entry.setNativeTime(get4ByteLittleEndian(zipDir, pos + 12)); |
| entry.compressedSize = get4ByteLittleEndian(zipDir, pos + 20); |
| entry.size = get4ByteLittleEndian(zipDir, pos + 24); |
| entry.offset = get4ByteLittleEndian(zipDir, pos + 42); |
| entryList.add(entry); |
| } |
| |
| return pos + 46 + |
| get2ByteLittleEndian(zipDir, pos + 28) + |
| get2ByteLittleEndian(zipDir, pos + 30) + |
| get2ByteLittleEndian(zipDir, pos + 32); |
| } |
| } |
| |
| /** |
| * Returns the last modified timestamp of a zip file. |
| * @return long |
| */ |
| public long getZipFileLastModified() throws IOException { |
| synchronized (this) { |
| checkIndex(); |
| return zipFileLastModified; |
| } |
| } |
| |
| /** ------------------------------------------------------------------------ |
| * DirectoryEntry class |
| * -------------------------------------------------------------------------*/ |
| |
| static class DirectoryEntry { |
| private boolean filesInited; |
| private boolean directoriesInited; |
| private boolean zipFileEntriesInited; |
| private boolean entriesInited; |
| |
| private long writtenOffsetOffset = 0; |
| |
| private RelativeDirectory dirName; |
| |
| private com.sun.tools.javac.util.List<String> zipFileEntriesFiles = com.sun.tools.javac.util.List.<String>nil(); |
| private com.sun.tools.javac.util.List<String> zipFileEntriesDirectories = com.sun.tools.javac.util.List.<String>nil(); |
| private com.sun.tools.javac.util.List<Entry> zipFileEntries = com.sun.tools.javac.util.List.<Entry>nil(); |
| |
| private List<Entry> entries = new ArrayList<Entry>(); |
| |
| private ZipFileIndex zipFileIndex; |
| |
| private int numEntries; |
| |
| DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index) { |
| filesInited = false; |
| directoriesInited = false; |
| entriesInited = false; |
| |
| this.dirName = dirName; |
| this.zipFileIndex = index; |
| } |
| |
| private com.sun.tools.javac.util.List<String> getFiles() { |
| if (!filesInited) { |
| initEntries(); |
| for (Entry e : entries) { |
| if (!e.isDir) { |
| zipFileEntriesFiles = zipFileEntriesFiles.append(e.name); |
| } |
| } |
| filesInited = true; |
| } |
| return zipFileEntriesFiles; |
| } |
| |
| private com.sun.tools.javac.util.List<String> getDirectories() { |
| if (!directoriesInited) { |
| initEntries(); |
| for (Entry e : entries) { |
| if (e.isDir) { |
| zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name); |
| } |
| } |
| directoriesInited = true; |
| } |
| return zipFileEntriesDirectories; |
| } |
| |
| private com.sun.tools.javac.util.List<Entry> getEntries() { |
| if (!zipFileEntriesInited) { |
| initEntries(); |
| zipFileEntries = com.sun.tools.javac.util.List.nil(); |
| for (Entry zfie : entries) { |
| zipFileEntries = zipFileEntries.append(zfie); |
| } |
| zipFileEntriesInited = true; |
| } |
| return zipFileEntries; |
| } |
| |
| private Entry getEntry(String rootName) { |
| initEntries(); |
| int index = Collections.binarySearch(entries, new Entry(dirName, rootName)); |
| if (index < 0) { |
| return null; |
| } |
| |
| return entries.get(index); |
| } |
| |
| private void initEntries() { |
| if (entriesInited) { |
| return; |
| } |
| |
| if (!zipFileIndex.readFromIndex) { |
| int from = -Arrays.binarySearch(zipFileIndex.entries, |
| new Entry(dirName, ZipFileIndex.MIN_CHAR)) - 1; |
| int to = -Arrays.binarySearch(zipFileIndex.entries, |
| new Entry(dirName, MAX_CHAR)) - 1; |
| |
| for (int i = from; i < to; i++) { |
| entries.add(zipFileIndex.entries[i]); |
| } |
| } else { |
| File indexFile = zipFileIndex.getIndexFile(); |
| if (indexFile != null) { |
| RandomAccessFile raf = null; |
| try { |
| raf = new RandomAccessFile(indexFile, "r"); |
| raf.seek(writtenOffsetOffset); |
| |
| for (int nFiles = 0; nFiles < numEntries; nFiles++) { |
| // Read the name bytes |
| int zfieNameBytesLen = raf.readInt(); |
| byte [] zfieNameBytes = new byte[zfieNameBytesLen]; |
| raf.read(zfieNameBytes); |
| String eName = new String(zfieNameBytes, "UTF-8"); |
| |
| // Read isDir |
| boolean eIsDir = raf.readByte() == (byte)0 ? false : true; |
| |
| // Read offset of bytes in the real Jar/Zip file |
| int eOffset = raf.readInt(); |
| |
| // Read size of the file in the real Jar/Zip file |
| int eSize = raf.readInt(); |
| |
| // Read compressed size of the file in the real Jar/Zip file |
| int eCsize = raf.readInt(); |
| |
| // Read java time stamp of the file in the real Jar/Zip file |
| long eJavaTimestamp = raf.readLong(); |
| |
| Entry rfie = new Entry(dirName, eName); |
| rfie.isDir = eIsDir; |
| rfie.offset = eOffset; |
| rfie.size = eSize; |
| rfie.compressedSize = eCsize; |
| rfie.javatime = eJavaTimestamp; |
| entries.add(rfie); |
| } |
| } catch (Throwable t) { |
| // Do nothing |
| } finally { |
| try { |
| if (raf != null) { |
| raf.close(); |
| } |
| } catch (Throwable t) { |
| // Do nothing |
| } |
| } |
| } |
| } |
| |
| entriesInited = true; |
| } |
| |
| List<Entry> getEntriesAsCollection() { |
| initEntries(); |
| |
| return entries; |
| } |
| } |
| |
| private boolean readIndex() { |
| if (triedToReadIndex || !usePreindexedCache) { |
| return false; |
| } |
| |
| boolean ret = false; |
| synchronized (this) { |
| triedToReadIndex = true; |
| RandomAccessFile raf = null; |
| try { |
| File indexFileName = getIndexFile(); |
| raf = new RandomAccessFile(indexFileName, "r"); |
| |
| long fileStamp = raf.readLong(); |
| if (zipFile.lastModified() != fileStamp) { |
| ret = false; |
| } else { |
| directories = new LinkedHashMap<RelativeDirectory, DirectoryEntry>(); |
| int numDirs = raf.readInt(); |
| for (int nDirs = 0; nDirs < numDirs; nDirs++) { |
| int dirNameBytesLen = raf.readInt(); |
| byte [] dirNameBytes = new byte[dirNameBytesLen]; |
| raf.read(dirNameBytes); |
| |
| RelativeDirectory dirNameStr = getRelativeDirectory(new String(dirNameBytes, "UTF-8")); |
| DirectoryEntry de = new DirectoryEntry(dirNameStr, this); |
| de.numEntries = raf.readInt(); |
| de.writtenOffsetOffset = raf.readLong(); |
| directories.put(dirNameStr, de); |
| } |
| ret = true; |
| zipFileLastModified = fileStamp; |
| } |
| } catch (Throwable t) { |
| // Do nothing |
| } finally { |
| if (raf != null) { |
| try { |
| raf.close(); |
| } catch (Throwable tt) { |
| // Do nothing |
| } |
| } |
| } |
| if (ret == true) { |
| readFromIndex = true; |
| } |
| } |
| |
| return ret; |
| } |
| |
| private boolean writeIndex() { |
| boolean ret = false; |
| if (readFromIndex || !usePreindexedCache) { |
| return true; |
| } |
| |
| if (!writeIndex) { |
| return true; |
| } |
| |
| File indexFile = getIndexFile(); |
| if (indexFile == null) { |
| return false; |
| } |
| |
| RandomAccessFile raf = null; |
| long writtenSoFar = 0; |
| try { |
| raf = new RandomAccessFile(indexFile, "rw"); |
| |
| raf.writeLong(zipFileLastModified); |
| writtenSoFar += 8; |
| |
| List<DirectoryEntry> directoriesToWrite = new ArrayList<DirectoryEntry>(); |
| Map<RelativeDirectory, Long> offsets = new HashMap<RelativeDirectory, Long>(); |
| raf.writeInt(directories.keySet().size()); |
| writtenSoFar += 4; |
| |
| for (RelativeDirectory dirName: directories.keySet()) { |
| DirectoryEntry dirEntry = directories.get(dirName); |
| |
| directoriesToWrite.add(dirEntry); |
| |
| // Write the dir name bytes |
| byte [] dirNameBytes = dirName.getPath().getBytes("UTF-8"); |
| int dirNameBytesLen = dirNameBytes.length; |
| raf.writeInt(dirNameBytesLen); |
| writtenSoFar += 4; |
| |
| raf.write(dirNameBytes); |
| writtenSoFar += dirNameBytesLen; |
| |
| // Write the number of files in the dir |
| List<Entry> dirEntries = dirEntry.getEntriesAsCollection(); |
| raf.writeInt(dirEntries.size()); |
| writtenSoFar += 4; |
| |
| offsets.put(dirName, new Long(writtenSoFar)); |
| |
| // Write the offset of the file's data in the dir |
| dirEntry.writtenOffsetOffset = 0L; |
| raf.writeLong(0L); |
| writtenSoFar += 8; |
| } |
| |
| for (DirectoryEntry de : directoriesToWrite) { |
| // Fix up the offset in the directory table |
| long currFP = raf.getFilePointer(); |
| |
| long offsetOffset = offsets.get(de.dirName).longValue(); |
| raf.seek(offsetOffset); |
| raf.writeLong(writtenSoFar); |
| |
| raf.seek(currFP); |
| |
| // Now write each of the files in the DirectoryEntry |
| List<Entry> list = de.getEntriesAsCollection(); |
| for (Entry zfie : list) { |
| // Write the name bytes |
| byte [] zfieNameBytes = zfie.name.getBytes("UTF-8"); |
| int zfieNameBytesLen = zfieNameBytes.length; |
| raf.writeInt(zfieNameBytesLen); |
| writtenSoFar += 4; |
| raf.write(zfieNameBytes); |
| writtenSoFar += zfieNameBytesLen; |
| |
| // Write isDir |
| raf.writeByte(zfie.isDir ? (byte)1 : (byte)0); |
| writtenSoFar += 1; |
| |
| // Write offset of bytes in the real Jar/Zip file |
| raf.writeInt(zfie.offset); |
| writtenSoFar += 4; |
| |
| // Write size of the file in the real Jar/Zip file |
| raf.writeInt(zfie.size); |
| writtenSoFar += 4; |
| |
| // Write compressed size of the file in the real Jar/Zip file |
| raf.writeInt(zfie.compressedSize); |
| writtenSoFar += 4; |
| |
| // Write java time stamp of the file in the real Jar/Zip file |
| raf.writeLong(zfie.getLastModified()); |
| writtenSoFar += 8; |
| } |
| } |
| } catch (Throwable t) { |
| // Do nothing |
| } finally { |
| try { |
| if (raf != null) { |
| raf.close(); |
| } |
| } catch(IOException ioe) { |
| // Do nothing |
| } |
| } |
| |
| return ret; |
| } |
| |
| public boolean writeZipIndex() { |
| synchronized (this) { |
| return writeIndex(); |
| } |
| } |
| |
| private File getIndexFile() { |
| if (zipIndexFile == null) { |
| if (zipFile == null) { |
| return null; |
| } |
| |
| zipIndexFile = new File((preindexedCacheLocation == null ? "" : preindexedCacheLocation) + |
| zipFile.getName() + ".index"); |
| } |
| |
| return zipIndexFile; |
| } |
| |
| public File getZipFile() { |
| return zipFile; |
| } |
| |
| File getAbsoluteFile() { |
| File absFile = (absFileRef == null ? null : absFileRef.get()); |
| if (absFile == null) { |
| absFile = zipFile.getAbsoluteFile(); |
| absFileRef = new SoftReference<File>(absFile); |
| } |
| return absFile; |
| } |
| |
| private RelativeDirectory getRelativeDirectory(String path) { |
| RelativeDirectory rd; |
| SoftReference<RelativeDirectory> ref = relativeDirectoryCache.get(path); |
| if (ref != null) { |
| rd = ref.get(); |
| if (rd != null) |
| return rd; |
| } |
| rd = new RelativeDirectory(path); |
| relativeDirectoryCache.put(path, new SoftReference<RelativeDirectory>(rd)); |
| return rd; |
| } |
| |
| static class Entry implements Comparable<Entry> { |
| public static final Entry[] EMPTY_ARRAY = {}; |
| |
| // Directory related |
| RelativeDirectory dir; |
| boolean isDir; |
| |
| // File related |
| String name; |
| |
| int offset; |
| int size; |
| int compressedSize; |
| long javatime; |
| |
| private int nativetime; |
| |
| public Entry(RelativePath path) { |
| this(path.dirname(), path.basename()); |
| } |
| |
| public Entry(RelativeDirectory directory, String name) { |
| this.dir = directory; |
| this.name = name; |
| } |
| |
| public String getName() { |
| return new RelativeFile(dir, name).getPath(); |
| } |
| |
| public String getFileName() { |
| return name; |
| } |
| |
| public long getLastModified() { |
| if (javatime == 0) { |
| javatime = dosToJavaTime(nativetime); |
| } |
| return javatime; |
| } |
| |
| // based on dosToJavaTime in java.util.Zip, but avoiding the |
| // use of deprecated Date constructor |
| private static long dosToJavaTime(int dtime) { |
| Calendar c = Calendar.getInstance(); |
| c.set(Calendar.YEAR, ((dtime >> 25) & 0x7f) + 1980); |
| c.set(Calendar.MONTH, ((dtime >> 21) & 0x0f) - 1); |
| c.set(Calendar.DATE, ((dtime >> 16) & 0x1f)); |
| c.set(Calendar.HOUR_OF_DAY, ((dtime >> 11) & 0x1f)); |
| c.set(Calendar.MINUTE, ((dtime >> 5) & 0x3f)); |
| c.set(Calendar.SECOND, ((dtime << 1) & 0x3e)); |
| c.set(Calendar.MILLISECOND, 0); |
| return c.getTimeInMillis(); |
| } |
| |
| void setNativeTime(int natTime) { |
| nativetime = natTime; |
| } |
| |
| public boolean isDirectory() { |
| return isDir; |
| } |
| |
| public int compareTo(Entry other) { |
| RelativeDirectory otherD = other.dir; |
| if (dir != otherD) { |
| int c = dir.compareTo(otherD); |
| if (c != 0) |
| return c; |
| } |
| return name.compareTo(other.name); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!(o instanceof Entry)) |
| return false; |
| Entry other = (Entry) o; |
| return dir.equals(other.dir) && name.equals(other.name); |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 7; |
| hash = 97 * hash + (this.dir != null ? this.dir.hashCode() : 0); |
| hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0); |
| return hash; |
| } |
| |
| @Override |
| public String toString() { |
| return isDir ? ("Dir:" + dir + " : " + name) : |
| (dir + ":" + name); |
| } |
| } |
| |
| /* |
| * Exception primarily used to implement a failover, used exclusively here. |
| */ |
| |
| static final class ZipFormatException extends IOException { |
| private static final long serialVersionUID = 8000196834066748623L; |
| protected ZipFormatException(String message) { |
| super(message); |
| } |
| |
| protected ZipFormatException(String message, Throwable cause) { |
| super(message, cause); |
| } |
| } |
| } |