// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.vfs; import com.google.common.base.Predicate; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * A FileSystem that provides a read-only filesystem view on a zip file. * Inherits the constraints imposed by ReadonlyFileSystem. */ @ThreadSafe public class ZipFileSystem extends ReadonlyFileSystem { private final ZipFile zipFile; /** * The sole purpose of this field is to hold a strong reference to all leaf * {@link Path}s which have a non-null "entry" field, preventing them from * being garbage-collected. (The leaf paths hold string references to their * parents, so we don't need to include them here.) * *
This is necessary because {@link Path}s may be recycled when they
* become unreachable, but the ZipFileSystem uses them to hold the {@link
* ZipEntry} for that path, if any. Without this additional strong
* reference, ZipEntries would seem to "disappear" during garbage collection.
*/
@SuppressWarnings("unused")
private final Object paths;
/**
* Constructs a ZipFileSystem from a zip file identified with a given path.
*/
public ZipFileSystem(Path zipPath) throws IOException {
// Throw some more specific exceptions than ZipFile does.
// We do this using File instead of Path, in case zipPath points to an
// InMemoryFileSystem. This case is not really supported but
// can occur in tests.
File file = zipPath.getPathFile();
if (!file.exists()) {
throw new FileNotFoundException(String.format("File '%s' does not exist", zipPath));
}
if (!file.isFile()) {
throw new IOException(String.format("'%s' is not a file", zipPath));
}
if (!file.canRead()) {
throw new IOException(String.format("File '%s' is not readable", zipPath));
}
this.zipFile = new ZipFile(file);
this.paths = populatePathTree();
}
// ZipPath extends Path with a set-once ZipEntry field.
// TODO(bazel-team): (2009) Delete class ZipPath, and perform the
// Path-to-ZipEntry lookup in {@link #zipEntry} and {@link
// #getDirectoryEntries}. Then this field becomes redundant.
@ThreadSafe
private static class ZipPath extends Path {
/**
* Non-null iff this file/directory exists. Set by setZipEntry for files
* explicitly mentioned in the zipfile's table of contents, or implicitly
* an ancestor of them.
*/
ZipEntry entry = null;
// Root path.
ZipPath(ZipFileSystem fileSystem) {
super(fileSystem);
}
// Non-root paths.
ZipPath(ZipFileSystem fileSystem, String name, ZipPath parent) {
super(fileSystem, name, parent);
}
void setZipEntry(ZipEntry entry) {
if (this.entry != null) {
throw new IllegalStateException("setZipEntry(" + entry
+ ") called twice!");
}
this.entry = entry;
// Ensure all parents of this path have a directory ZipEntry:
for (ZipPath path = (ZipPath) getParentDirectory();
path != null && path.entry == null;
path = (ZipPath) path.getParentDirectory()) {
// Note, the ZipEntry for the root path is called "//", but that's ok.
path.setZipEntry(new ZipEntry(path + "/")); // trailing "/" => isDir
}
}
@Override
protected ZipPath createChildPath(String childName) {
return new ZipPath((ZipFileSystem) getFileSystem(), childName, this);
}
}
/**
* Scans the Zip file and associates a ZipEntry with each filename
* (ZipPath) that is mentioned in the table of contents. Returns a
* collection of all corresponding Paths.
*/
private Collection