package com.sun.tools.javac.zip;
import java.io.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.*;
/** This class implements building of index of a zip archive and access to it's context.
* It also uses prebuild index if available. It supports invocations where it will
* serialize an optimized zip index file to disk.
*
* In oreder to use secondary index file make sure the option "usezipindex" is 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
* "-XDcachezipindexdir=<directory>". If this flag is not provided, the dfault location is
* the value of the "java.io.tmpdir" system property.
*
* If key "-XDwritezipindexfiles" is specified, there will be new optimized index file
* created for each archive, used by the compiler for compilation, at location,
* specified by "cachezipindexdir" option.
*
* If nonBatchMode option is specified (-XDnonBatchMode) 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.
*/
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 Map<File, ZipFileIndex> zipFileIndexCache = new HashMap<File, ZipFileIndex>();
private static ReentrantLock lock = new ReentrantLock();
private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
private Map<String, DirectoryEntry> directories = Collections.<String, DirectoryEntry>emptyMap();
private Set<String> allDirs = Collections.<String>emptySet();
// ZipFileIndex data entries
private File zipFile;
private long zipFileLastModified = NOT_MODIFIED;
private RandomAccessFile zipRandomFile;
private ZipFileIndexEntry[] entries;
private boolean readFromIndex = false;
private File zipIndexFile = null;
private boolean triedToReadIndex = false;
private int symbolFilePrefixLength = 0;
private boolean hasPopulatedData = false;
private long lastReferenceTimeStamp = NOT_MODIFIED;
private boolean usePreindexedCache = false;
private String preindexedCacheLocation = null;
private boolean writeIndex = false;
/**
* Returns a list of all ZipFileIndex entries
*
* @return A list of ZipFileIndex entries, or an empty list
*/
public static List<ZipFileIndex> getZipFileIndexes() {
return getZipFileIndexes(false);
}
/**
* Returns a list of all ZipFileIndex entries
*
* @param openedOnly If true it returns a list of only opened ZipFileIndex entries, otherwise
* all ZipFileEntry(s) are included into the list.
* @return A list of ZipFileIndex entries, or an empty list
*/
public static List<ZipFileIndex> getZipFileIndexes(boolean openedOnly) {
List<ZipFileIndex> zipFileIndexes = new ArrayList<ZipFileIndex>();
lock.lock();
try {
zipFileIndexes.addAll(zipFileIndexCache.values());
if (openedOnly) {
for(ZipFileIndex elem : zipFileIndexes) {
if (!elem.isOpen()) {
zipFileIndexes.remove(elem);
}
}
}
}
finally {
lock.unlock();
}
return zipFileIndexes;
}
public boolean isOpen() {
lock.lock();
try {
return zipRandomFile != null;
}
finally {
lock.unlock();
}
}
public static ZipFileIndex getZipFileIndex(File zipFile, int symbolFilePrefixLen, boolean useCache, String cacheLocation, boolean writeIndex) throws IOException {
ZipFileIndex zi = null;
lock.lock();
try {
zi = getExistingZipIndex(zipFile);
if (zi == null || (zi != null && zipFile.lastModified() != zi.zipFileLastModified)) {
zi = new ZipFileIndex(zipFile, symbolFilePrefixLen, writeIndex,
useCache, cacheLocation);
zipFileIndexCache.put(zipFile, zi);
}
}
finally {
lock.unlock();
}
return zi;
}
public static ZipFileIndex getExistingZipIndex(File zipFile) {
lock.lock();
try {
return zipFileIndexCache.get(zipFile);
}
finally {
lock.unlock();
}
}
public static void clearCache() {
lock.lock();
try {
zipFileIndexCache.clear();
}
finally {
lock.unlock();
}
}
public static void clearCache(long timeNotUsed) {
lock.lock();
try {
Iterator<File> cachedFileIterator = zipFileIndexCache.keySet().iterator();
while (cachedFileIterator.hasNext()) {
File cachedFile = cachedFileIterator.next();
ZipFileIndex cachedZipIndex = zipFileIndexCache.get(cachedFile);
if (cachedZipIndex != null) {
long timeToTest = cachedZipIndex.lastReferenceTimeStamp + timeNotUsed;
if (timeToTest < cachedZipIndex.lastReferenceTimeStamp || // Overflow...
System.currentTimeMillis() > timeToTest) {
zipFileIndexCache.remove(cachedFile);
}
}
}
}
finally {
lock.unlock();
}
}
public static void removeFromCache(File file) {
lock.lock();
try {
zipFileIndexCache.remove(file);
}
finally {
lock.unlock();
}
}
/** Sets already opened list of ZipFileIndexes from an outside client
* of the compiler. This functionality should be used in a non-batch clients of the compiler.
*/
public static void setOpenedIndexes(List<ZipFileIndex>indexes) throws IllegalStateException {
lock.lock();
try {
if (zipFileIndexCache.isEmpty()) {
throw new IllegalStateException("Setting opened indexes should be called only when the ZipFileCache is empty. Call JavacFileManager.flush() before calling this method.");
}
for (ZipFileIndex zfi : indexes) {
zipFileIndexCache.put(zfi.zipFile, zfi);
}
}
finally {
lock.unlock();
}
}
private ZipFileIndex(File zipFile, int symbolFilePrefixLen, boolean writeIndex,
boolean useCache, String cacheLocation) throws IOException {
this.zipFile = zipFile;
this.symbolFilePrefixLength = symbolFilePrefixLen;
this.writeIndex = writeIndex;
this.usePreindexedCache = useCache;
this.preindexedCacheLocation = cacheLocation;
if (zipFile != null) {
this.zipFileLastModified = zipFile.lastModified();
}
// Validate integrity of the zip file
checkIndex();
}
public String toString() {
return "ZipFileIndex of file:(" + zipFile + ")";
}
// Just in case...
protected void finalize() {
closeFile();
}
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.<String, DirectoryEntry>emptyMap();
allDirs = Collections.<String>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 = ZipFileIndexEntry.EMPTY_ARRAY;
directories = Collections.<String, DirectoryEntry>emptyMap();
zipFileLastModified = NOT_MODIFIED;
allDirs = Collections.<String>emptySet();
}
public void close() {
lock.lock();
try {
writeIndex();
closeFile();
}
finally {
lock.unlock();
}
}
private void closeFile() {
if (zipRandomFile != null) {
try {
zipRandomFile.close();
} catch (IOException ex) {
}
zipRandomFile = null;
}
}
/**
* Returns the ZipFileIndexEntry for an absolute path, if there is one.
*/
public ZipFileIndexEntry getZipIndexEntry(String path) {
if (File.separatorChar != '/') {
path = path.replace('/', File.separatorChar);
}
lock.lock();
try {
checkIndex();
String lookFor = "";
int lastSepIndex = path.lastIndexOf(File.separatorChar);
boolean noSeparator = false;
if (lastSepIndex == -1) {
noSeparator = true;
}
DirectoryEntry de = directories.get(noSeparator ? "" : path.substring(0, lastSepIndex));
lookFor = path.substring(noSeparator ? 0 : lastSepIndex + 1);
return de == null ? null : de.getEntry(lookFor);
}
catch (IOException e) {
return null;
}
finally {
lock.unlock();
}
}
/**
* Returns a javac List of filenames within an absolute path in the ZipFileIndex.
*/
public com.sun.tools.javac.util.List<String> getFiles(String path) {
if (File.separatorChar != '/') {
path = path.replace('/', File.separatorChar);
}
lock.lock();
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();
}
finally {
lock.unlock();
}
}
public List<String> getAllDirectories(String path) {
if (File.separatorChar != '/') {
path = path.replace('/', File.separatorChar);
}
lock.lock();
try {
checkIndex();
path = path.intern();
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();
}
finally {
lock.unlock();
}
}
public Set<String> getAllDirectories() {
lock.lock();
try {
checkIndex();
if (allDirs == Collections.EMPTY_SET) {
Set<String> alldirs = new HashSet<String>();
Iterator<String> dirsIter = directories.keySet().iterator();
while (dirsIter.hasNext()) {
alldirs.add(new String(dirsIter.next()));
}
allDirs = alldirs;
}
return allDirs;
}
catch (IOException e) {
return Collections.<String>emptySet();
}
finally {
lock.unlock();
}
}
/**
* 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 boolean contains(String path) {
lock.lock();
try {
checkIndex();
return getZipIndexEntry(path) != null;
}
catch (IOException e) {
return false;
}
finally {
lock.unlock();
}
}
public boolean isDirectory(String path) throws IOException {
lock.lock();
try {
// The top level in a zip file is always a directory.
if (path.length() == 0) {
lastReferenceTimeStamp = System.currentTimeMillis();
return true;
}
if (File.separatorChar != '/')
path = path.replace('/', File.separatorChar);
checkIndex();
return directories.get(path) != null;
}
finally {
lock.unlock();
}
}
public long getLastModified(String path) throws IOException {
lock.lock();
try {
ZipFileIndexEntry entry = getZipIndexEntry(path);
if (entry == null)
throw new FileNotFoundException();
return entry.getLastModified();
}
finally {
lock.unlock();
}
}
public int length(String path) throws IOException {
lock.lock();
try {
ZipFileIndexEntry 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;
}
}
finally {
lock.unlock();
}
}
public byte[] read(String path) throws IOException {
lock.lock();
try {
ZipFileIndexEntry entry = getZipIndexEntry(path);
if (entry == null)
throw new FileNotFoundException(MessageFormat.format("Path not found in ZIP: {0}", path));
return read(entry);
}
finally {
lock.unlock();
}
}
public byte[] read(ZipFileIndexEntry entry) throws IOException {
lock.lock();
try {
openFile();
byte[] result = readBytes(entry);
closeFile();
return result;
}
finally {
lock.unlock();
}
}
public int read(String path, byte[] buffer) throws IOException {
lock.lock();
try {
ZipFileIndexEntry entry = getZipIndexEntry(path);
if (entry == null)
throw new FileNotFoundException();
return read(entry, buffer);
}
finally {
lock.unlock();
}
}
public int read(ZipFileIndexEntry entry, byte[] buffer)
throws IOException {
lock.lock();
try {
int result = readBytes(entry, buffer);
return result;
}
finally {
lock.unlock();
}
}
private byte[] readBytes(ZipFileIndexEntry 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(ZipFileIndexEntry 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(ZipFileIndexEntry 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 static Inflater inflater;
private int inflate(byte[] src, byte[] dest) {
// construct the inflater object or reuse an existing one
if (inflater == null)
inflater = new Inflater(true);
synchronized (inflater) {
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 String 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;
findCENRecord(start, end);
}
/*
* 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) + 2];
zipDir[0] = endbuf[i + 10];
zipDir[1] = endbuf[i + 11];
zipRandomFile.seek(start + get4ByteLittleEndian(endbuf, i + 16));
zipRandomFile.readFully(zipDir, 2, zipDir.length - 2);
return;
} else {
endbufend = endbufpos + 21;
}
}
throw new ZipException("cannot read zip file");
}
private void buildIndex() throws IOException {
int entryCount = get2ByteLittleEndian(zipDir, 0);
entries = new ZipFileIndexEntry[entryCount];
// Add each of the files
if (entryCount > 0) {
directories = new HashMap<String, DirectoryEntry>();
ArrayList<ZipFileIndexEntry> entryList = new ArrayList<ZipFileIndexEntry>();
int pos = 2;
for (int i = 0; i < entryCount; i++) {
pos = readEntry(pos, entryList, directories);
}
// Add the accumulated dirs into the same list
Iterator i = directories.keySet().iterator();
while (i.hasNext()) {
ZipFileIndexEntry zipFileIndexEntry = new ZipFileIndexEntry( (String) i.next());
zipFileIndexEntry.isDir = true;
entryList.add(zipFileIndexEntry);
}
entries = entryList.toArray(new ZipFileIndexEntry[entryList.size()]);
Arrays.sort(entries);
} else {
cleanupState();
}
}
private int readEntry(int pos, List<ZipFileIndexEntry> entryList,
Map<String, 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;
}
// Use the OS's path separator. Keep the position of the last one.
for (int index = fileStart; index < fileEnd; index++) {
byte nextByte = zipDir[index];
if (nextByte == (byte)'\\' || nextByte == (byte)'/') {
zipDir[index] = (byte)File.separatorChar;
fileStart = index + 1;
}
}
String directory = null;
if (fileStart == dirStart)
directory = "";
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 = new String(zipDir, dirStart, lastLen, "UTF-8").intern();
lastDir = directory;
// Enter also all the parent directories
String tempDirectory = directory;
while (directories.get(tempDirectory) == null) {
directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex));
int separator = tempDirectory.lastIndexOf(File.separatorChar);
if (separator == -1)
break;
tempDirectory = tempDirectory.substring(0, separator);
}
}
else {
directory = directory.intern();
if (directories.get(directory) == null) {
directories.put(directory, new DirectoryEntry(directory, zipFileIndex));
}
}
// For each dir create also a file
if (fileStart != fileEnd) {
ZipFileIndexEntry entry = new ZipFileIndexEntry(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 {
lock.lock();
try {
checkIndex();
return zipFileLastModified;
}
finally {
lock.unlock();
}
}
/** ------------------------------------------------------------------------
* DirectoryEntry class
* -------------------------------------------------------------------------*/
static class DirectoryEntry {
private boolean filesInited;
private boolean directoriesInited;
private boolean zipFileEntriesInited;
private boolean entriesInited;
private long writtenOffsetOffset = 0;
private String 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<ZipFileIndexEntry> zipFileEntries = com.sun.tools.javac.util.List.<ZipFileIndexEntry>nil();
private List<ZipFileIndexEntry> entries = new ArrayList<ZipFileIndexEntry>();
private ZipFileIndex zipFileIndex;
private int numEntries;
DirectoryEntry(String dirName, ZipFileIndex index) {
filesInited = false;
directoriesInited = false;
entriesInited = false;
if (File.separatorChar == '/') {
dirName.replace('\\', '/');
}
else {
dirName.replace('/', '\\');
}
this.dirName = dirName.intern();
this.zipFileIndex = index;
}
private com.sun.tools.javac.util.List<String> getFiles() {
if (filesInited) {
return zipFileEntriesFiles;
}
initEntries();
for (ZipFileIndexEntry 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) {
return zipFileEntriesFiles;
}
initEntries();
for (ZipFileIndexEntry e : entries) {
if (e.isDir) {
zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name);
}
}
directoriesInited = true;
return zipFileEntriesDirectories;
}
private com.sun.tools.javac.util.List<ZipFileIndexEntry> getEntries() {
if (zipFileEntriesInited) {
return zipFileEntries;
}
initEntries();
zipFileEntries = com.sun.tools.javac.util.List.nil();
for (ZipFileIndexEntry zfie : entries) {
zipFileEntries = zipFileEntries.append(zfie);
}
zipFileEntriesInited = true;
return zipFileEntries;
}
private ZipFileIndexEntry getEntry(String rootName) {
initEntries();
int index = Collections.binarySearch(entries, new ZipFileIndexEntry(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 ZipFileIndexEntry(dirName, ZipFileIndex.MIN_CHAR)) - 1;
int to = -Arrays.binarySearch(zipFileIndex.entries,
new ZipFileIndexEntry(dirName, MAX_CHAR)) - 1;
boolean emptyList = false;
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();
ZipFileIndexEntry rfie = new ZipFileIndexEntry(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<ZipFileIndexEntry> getEntriesAsCollection() {
initEntries();
return entries;
}
}
private boolean readIndex() {
if (triedToReadIndex || !usePreindexedCache) {
return false;
}
boolean ret = false;
lock.lock();
try {
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 HashMap<String, DirectoryEntry>();
int numDirs = raf.readInt();
for (int nDirs = 0; nDirs < numDirs; nDirs++) {
int dirNameBytesLen = raf.readInt();
byte [] dirNameBytes = new byte[dirNameBytesLen];
raf.read(dirNameBytes);
String dirNameStr = 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;
}
}
finally {
lock.unlock();
}
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;
Iterator<String> iterDirName = directories.keySet().iterator();
List<DirectoryEntry> directoriesToWrite = new ArrayList<DirectoryEntry>();
Map<String, Long> offsets = new HashMap<String, Long>();
raf.writeInt(directories.keySet().size());
writtenSoFar += 4;
while(iterDirName.hasNext()) {
String dirName = iterDirName.next();
DirectoryEntry dirEntry = directories.get(dirName);
directoriesToWrite.add(dirEntry);
// Write the dir name bytes
byte [] dirNameBytes = dirName.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 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<ZipFileIndexEntry> entries = de.getEntriesAsCollection();
for (ZipFileIndexEntry zfie : entries) {
// 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() {
lock.lock();
try {
return writeIndex();
}
finally {
lock.unlock();
}
}
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;
}
}