1170 lines
40 KiB
Java
1170 lines
40 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|
|
}
|