blob: aa0bf2ff66ba981bfad63099e2482c35dca7e5fb [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.io;
import com.facebook.buck.util.environment.Platform;
import com.facebook.buck.zip.CustomZipEntry;
import com.facebook.buck.zip.CustomZipOutputStream;
import com.facebook.buck.zip.ZipOutputStreams;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.channels.Channels;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
/**
* An injectable service for interacting with the filesystem relative to the project root.
*/
public class ProjectFilesystem {
/**
* Controls the behavior of how the source should be treated when copying.
*/
public enum CopySourceMode {
/**
* Copy the single source file into the destination path.
*/
FILE,
/**
* Treat the source as a directory and copy each file inside it
* to the destination path, which must be a directory.
*/
DIRECTORY_CONTENTS_ONLY,
/**
* Treat the source as a directory. Copy the directory and its
* contents to the destination path, which must be a directory.
*/
DIRECTORY_AND_CONTENTS,
}
private final Path projectRoot;
private final Function<Path, Path> pathAbsolutifier;
private final ImmutableSet<Path> ignorePaths;
// Defaults to false, and so paths should be valid.
@VisibleForTesting
protected boolean ignoreValidityOfPaths;
public ProjectFilesystem(Path projectRoot) {
this(projectRoot, ImmutableSet.<Path>of());
}
/**
* There should only be one {@link ProjectFilesystem} created per process.
* <p>
* When creating a {@code ProjectFilesystem} for a test, rather than create a filesystem with an
* arbitrary argument for the project root, such as {@code new File(".")}, prefer the creation of
* a mock filesystem via EasyMock instead. Note that there are cases (such as integration tests)
* where specifying {@code new File(".")} as the project root might be the appropriate thing.
*/
public ProjectFilesystem(Path projectRoot, ImmutableSet<Path> ignorePaths) {
this(projectRoot.getFileSystem(), projectRoot, ignorePaths);
}
protected ProjectFilesystem(FileSystem vfs, Path projectRoot, ImmutableSet<Path> ignorePaths) {
Preconditions.checkArgument(Files.isDirectory(projectRoot));
Preconditions.checkState(vfs.equals(projectRoot.getFileSystem()));
this.projectRoot = projectRoot;
this.pathAbsolutifier = new Function<Path, Path>() {
@Override
public Path apply(Path path) {
return resolve(path);
}
};
this.ignorePaths = MorePaths.filterForSubpaths(ignorePaths, this.projectRoot);
this.ignoreValidityOfPaths = false;
}
public Path getRootPath() {
return projectRoot;
}
/**
* @return the specified {@code path} resolved against {@link #getRootPath()} to an absolute path.
*/
public Path resolve(Path path) {
return getRootPath().resolve(path).toAbsolutePath().normalize();
}
public Path resolve(String path) {
return getRootPath().resolve(path).toAbsolutePath().normalize();
}
/**
* @return A {@link Function} that applies {@link #resolve(Path)} to its parameter.
*/
public Function<Path, Path> getAbsolutifier() {
return pathAbsolutifier;
}
/**
* @return A {@link ImmutableSet} of {@link Path} objects to have buck ignore. All paths will be
* relative to the {@link ProjectFilesystem#getRootPath()}.
*/
public ImmutableSet<Path> getIgnorePaths() {
return ignorePaths;
}
/**
* // @deprecated Prefer operating on {@code Path}s directly, replaced by
* {@link #getPathForRelativePath(java.nio.file.Path)}.
*/
public File getFileForRelativePath(String pathRelativeToProjectRoot) {
return pathRelativeToProjectRoot.isEmpty()
? projectRoot.toFile()
: getPathForRelativePath(Paths.get(pathRelativeToProjectRoot)).toFile();
}
/**
* // @deprecated Prefer operating on {@code Path}s directly, replaced by
* {@link #getPathForRelativePath(java.nio.file.Path)}.
*/
public File getFileForRelativePath(Path pathRelativeToProjectRoot) {
return getPathForRelativePath(pathRelativeToProjectRoot).toFile();
}
public Path getPathForRelativePath(Path pathRelativeToProjectRoot) {
return projectRoot.resolve(pathRelativeToProjectRoot);
}
/**
* As {@link #getFileForRelativePath(java.nio.file.Path)}, but with the added twist that the
* existence of the path is checked before returning.
*/
public Path getPathForRelativeExistingPath(Path pathRelativeToProjectRoot) {
Path file = getPathForRelativePath(pathRelativeToProjectRoot);
if (ignoreValidityOfPaths) {
return file;
}
// TODO(mbolin): Eliminate this temporary exemption for symbolic links.
if (Files.isSymbolicLink(file)) {
return file;
}
if (!Files.exists(file)) {
throw new RuntimeException(
String.format("Not an ordinary file: '%s'.", pathRelativeToProjectRoot));
}
return file;
}
public boolean exists(Path pathRelativeToProjectRoot) {
return Files.exists(getPathForRelativePath(pathRelativeToProjectRoot));
}
public long getFileSize(Path pathRelativeToProjectRoot) throws IOException {
Path path = getPathForRelativePath(pathRelativeToProjectRoot);
if (!Files.isRegularFile(path)) {
throw new IOException("Cannot get size of " + path + " because it is not an ordinary file.");
}
return Files.size(path);
}
/**
* Deletes a file specified by its path relative to the project root.
* @param pathRelativeToProjectRoot path to the file
* @return true if the file was successfully deleted, false otherwise
*/
public boolean deleteFileAtPath(Path pathRelativeToProjectRoot) {
try {
Files.delete(getPathForRelativePath(pathRelativeToProjectRoot));
return true;
} catch (IOException e) {
return false;
}
}
public Properties readPropertiesFile(Path pathToPropertiesFileRelativeToProjectRoot)
throws IOException {
Properties properties = new Properties();
Path propertiesFile = getPathForRelativePath(pathToPropertiesFileRelativeToProjectRoot);
if (Files.exists(propertiesFile)) {
properties.load(Files.newBufferedReader(propertiesFile, Charsets.UTF_8));
return properties;
} else {
throw new FileNotFoundException(propertiesFile.toString());
}
}
/**
* Checks whether there is a normal file at the specified path.
*/
public boolean isFile(Path pathRelativeToProjectRoot) {
return Files.isRegularFile(
getPathForRelativePath(pathRelativeToProjectRoot));
}
public boolean isHidden(Path pathRelativeToProjectRoot) throws IOException {
return Files.isHidden(getPathForRelativePath(pathRelativeToProjectRoot));
}
/**
* Similar to {@link #walkFileTree(Path, FileVisitor)} except this takes in a path relative to
* the project root.
*/
public void walkRelativeFileTree(
Path pathRelativeToProjectRoot,
final FileVisitor<Path> fileVisitor) throws IOException {
walkRelativeFileTree(pathRelativeToProjectRoot,
EnumSet.of(FileVisitOption.FOLLOW_LINKS),
fileVisitor);
}
/**
* Walks a project-root relative file tree with a visitor and visit options.
*/
public void walkRelativeFileTree(
Path pathRelativeToProjectRoot,
EnumSet<FileVisitOption> visitOptions,
final FileVisitor<Path> fileVisitor) throws IOException {
Path rootPath = getPathForRelativePath(pathRelativeToProjectRoot);
Files.walkFileTree(
rootPath,
visitOptions,
Integer.MAX_VALUE,
new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(
Path dir, BasicFileAttributes attrs) throws IOException {
return fileVisitor.preVisitDirectory(projectRoot.relativize(dir), attrs);
}
@Override
public FileVisitResult visitFile(
Path file, BasicFileAttributes attrs) throws IOException {
return fileVisitor.visitFile(projectRoot.relativize(file), attrs);
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return fileVisitor.visitFileFailed(projectRoot.relativize(file), exc);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
return fileVisitor.postVisitDirectory(projectRoot.relativize(dir), exc);
}
});
}
/**
* Allows {@link Files#walkFileTree} to be faked in tests.
*/
public void walkFileTree(Path root, FileVisitor<Path> fileVisitor) throws IOException {
Files.walkFileTree(root, fileVisitor);
}
public ImmutableSet<Path> getFilesUnderPath(Path pathRelativeToProjectRoot) throws IOException {
return getFilesUnderPath(pathRelativeToProjectRoot, Predicates.<Path>alwaysTrue());
}
public ImmutableSet<Path> getFilesUnderPath(
Path pathRelativeToProjectRoot,
Predicate<Path> predicate) throws IOException {
return getFilesUnderPath(
pathRelativeToProjectRoot,
predicate,
EnumSet.of(FileVisitOption.FOLLOW_LINKS));
}
public ImmutableSet<Path> getFilesUnderPath(
Path pathRelativeToProjectRoot,
final Predicate<Path> predicate,
EnumSet<FileVisitOption> visitOptions) throws IOException {
final ImmutableSet.Builder<Path> paths = ImmutableSet.builder();
walkRelativeFileTree(
pathRelativeToProjectRoot,
visitOptions,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attributes) {
if (predicate.apply(path)) {
paths.add(path);
}
return FileVisitResult.CONTINUE;
}
});
return paths.build();
}
/**
* Allows {@link Files#isDirectory} to be faked in tests.
*/
public boolean isDirectory(Path child, LinkOption... linkOptions) {
return Files.isDirectory(child, linkOptions);
}
/**
* Allows {@link java.io.File#listFiles} to be faked in tests.
*
* // @deprecated Replaced by {@link #getDirectoryContents}
*/
public File[] listFiles(Path pathRelativeToProjectRoot) throws IOException {
Collection<Path> paths = getDirectoryContents(pathRelativeToProjectRoot);
File[] result = new File[paths.size()];
return Collections2.transform(paths, new Function<Path, File>() {
@Override
public File apply(Path input) {
return input.toFile();
}
}).toArray(result);
}
public ImmutableCollection<Path> getDirectoryContents(Path pathRelativeToProjectRoot)
throws IOException {
Path path = getPathForRelativePath(pathRelativeToProjectRoot);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
return ImmutableList.copyOf(stream);
}
}
@VisibleForTesting
protected PathListing.PathModifiedTimeFetcher getLastModifiedTimeFetcher() {
return new PathListing.PathModifiedTimeFetcher() {
@Override
public FileTime getLastModifiedTime(Path path) throws IOException {
return FileTime.fromMillis(ProjectFilesystem.this.getLastModifiedTime(path));
}
};
}
/**
* Returns the files inside {@code pathRelativeToProjectRoot} which match
* {@code globPattern}, ordered in descending last modified time order.
*/
public ImmutableSortedSet<Path> getSortedMatchingDirectoryContents(
Path pathRelativeToProjectRoot,
String globPattern)
throws IOException {
Path path = getPathForRelativePath(pathRelativeToProjectRoot);
return PathListing.listMatchingPaths(
path,
globPattern,
getLastModifiedTimeFetcher());
}
public long getLastModifiedTime(Path pathRelativeToProjectRoot) throws IOException {
Path path = getPathForRelativePath(pathRelativeToProjectRoot);
return Files.getLastModifiedTime(path).toMillis();
}
/**
* Recursively delete everything under the specified path.
*/
public void rmdir(Path pathRelativeToProjectRoot) throws IOException {
MoreFiles.rmdir(resolve(pathRelativeToProjectRoot));
}
/**
* Resolves the relative path against the project root and then calls
* {@link Files#createDirectories(java.nio.file.Path,
* java.nio.file.attribute.FileAttribute[])}
*/
public void mkdirs(Path pathRelativeToProjectRoot) throws IOException {
Files.createDirectories(resolve(pathRelativeToProjectRoot));
}
/**
* // @deprecated Prefer operating on {@code Path}s directly, replaced by
* {@link #createParentDirs(java.nio.file.Path)}.
*/
public void createParentDirs(String pathRelativeToProjectRoot) throws IOException {
Path file = getPathForRelativePath(Paths.get(pathRelativeToProjectRoot));
mkdirs(file.getParent());
}
/**
* @param pathRelativeToProjectRoot Must identify a file, not a directory. (Unfortunately, we have
* no way to assert this because the path is not expected to exist yet.)
*/
public void createParentDirs(Path pathRelativeToProjectRoot) throws IOException {
Path file = resolve(pathRelativeToProjectRoot);
Path directory = file.getParent();
mkdirs(directory);
}
/**
* Writes each line in {@code lines} with a trailing newline to a file at the specified path.
* <p>
* The parent path of {@code pathRelativeToProjectRoot} must exist.
*/
public void writeLinesToPath(
Iterable<String> lines,
Path pathRelativeToProjectRoot,
FileAttribute<?>... attrs)
throws IOException {
try (Writer writer =
new BufferedWriter(
new OutputStreamWriter(newFileOutputStream(pathRelativeToProjectRoot, attrs)))) {
for (String line : lines) {
writer.write(line);
writer.write('\n');
}
}
}
public void writeContentsToPath(
String contents,
Path pathRelativeToProjectRoot,
FileAttribute<?>... attrs)
throws IOException {
writeBytesToPath(contents.getBytes(Charsets.UTF_8), pathRelativeToProjectRoot, attrs);
}
public void writeBytesToPath(
byte[] bytes,
Path pathRelativeToProjectRoot,
FileAttribute<?>... attrs) throws IOException {
try (OutputStream outputStream = newFileOutputStream(pathRelativeToProjectRoot, attrs)) {
outputStream.write(bytes);
}
}
public OutputStream newFileOutputStream(
Path pathRelativeToProjectRoot,
FileAttribute<?>... attrs)
throws IOException {
return new BufferedOutputStream(
Channels.newOutputStream(
Files.newByteChannel(
getPathForRelativePath(pathRelativeToProjectRoot),
ImmutableSet.<OpenOption>of(
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE),
attrs)));
}
public <A extends BasicFileAttributes> A readAttributes(
Path pathRelativeToProjectRoot,
Class<A> type,
LinkOption... options)
throws IOException {
return Files.readAttributes(
getPathForRelativePath(pathRelativeToProjectRoot), type, options);
}
public InputStream newFileInputStream(Path pathRelativeToProjectRoot)
throws IOException {
return new BufferedInputStream(
Files.newInputStream(getPathForRelativePath(pathRelativeToProjectRoot)));
}
/**
* @param inputStream Source of the bytes. This method does not close this stream.
*/
public void copyToPath(
InputStream inputStream,
Path pathRelativeToProjectRoot,
CopyOption... options)
throws IOException {
Files.copy(inputStream, getPathForRelativePath(pathRelativeToProjectRoot),
options);
}
public Optional<String> readFileIfItExists(Path pathRelativeToProjectRoot) {
Path fileToRead = getPathForRelativePath(pathRelativeToProjectRoot);
return readFileIfItExists(fileToRead, pathRelativeToProjectRoot.toString());
}
private Optional<String> readFileIfItExists(Path fileToRead, String pathRelativeToProjectRoot) {
if (Files.isRegularFile(fileToRead)) {
String contents;
try {
contents = new String(Files.readAllBytes(fileToRead), Charsets.UTF_8);
} catch (IOException e) {
// Alternatively, we could return Optional.absent(), though something seems suspicious if we
// have already verified that fileToRead is a file and then we cannot read it.
throw new RuntimeException("Error reading " + pathRelativeToProjectRoot, e);
}
return Optional.of(contents);
} else {
return Optional.absent();
}
}
/**
* Attempts to open the file for future read access. Returns {@link Optional#absent()} if the file
* does not exist.
*/
public Optional<Reader> getReaderIfFileExists(Path pathRelativeToProjectRoot) {
Path fileToRead = getPathForRelativePath(pathRelativeToProjectRoot);
if (Files.isRegularFile(fileToRead)) {
try {
return Optional.of(
(Reader) new BufferedReader(
new InputStreamReader(newFileInputStream(pathRelativeToProjectRoot))));
} catch (Exception e) {
throw new RuntimeException("Error reading " + pathRelativeToProjectRoot, e);
}
} else {
return Optional.absent();
}
}
/**
* Attempts to read the first line of the file specified by the relative path. If the file does
* not exist, is empty, or encounters an error while being read, {@link Optional#absent()} is
* returned. Otherwise, an {@link Optional} with the first line of the file will be returned.
*
* // @deprecated PRefero operation on {@code Path}s directly, replaced by
* {@link #readFirstLine(java.nio.file.Path)}
*/
public Optional<String> readFirstLine(String pathRelativeToProjectRoot) {
return readFirstLine(Paths.get(pathRelativeToProjectRoot));
}
/**
* Attempts to read the first line of the file specified by the relative path. If the file does
* not exist, is empty, or encounters an error while being read, {@link Optional#absent()} is
* returned. Otherwise, an {@link Optional} with the first line of the file will be returned.
*/
public Optional<String> readFirstLine(Path pathRelativeToProjectRoot) {
Path file = getPathForRelativePath(pathRelativeToProjectRoot);
return readFirstLineFromFile(file);
}
/**
* Attempts to read the first line of the specified file. If the file does not exist, is empty,
* or encounters an error while being read, {@link Optional#absent()} is returned. Otherwise, an
* {@link Optional} with the first line of the file will be returned.
*/
public Optional<String> readFirstLineFromFile(Path file) {
try {
return Optional.fromNullable(
Files.newBufferedReader(file, Charsets.UTF_8).readLine());
} catch (IOException e) {
// Because the file is not even guaranteed to exist, swallow the IOException.
return Optional.absent();
}
}
public List<String> readLines(Path pathRelativeToProjectRoot) throws IOException {
Path file = getPathForRelativePath(pathRelativeToProjectRoot);
return Files.readAllLines(file, Charsets.UTF_8);
}
/**
* // @deprecated Prefer operation on {@code Path}s directly, replaced by
* {@link Files#newInputStream(java.nio.file.Path, java.nio.file.OpenOption...)}.
*/
public InputStream getInputStreamForRelativePath(Path path) throws IOException {
Path file = getPathForRelativePath(path);
return Files.newInputStream(file);
}
public String computeSha1(Path pathRelativeToProjectRoot) throws IOException {
Path fileToHash = getPathForRelativePath(pathRelativeToProjectRoot);
return Hashing.sha1().hashBytes(Files.readAllBytes(fileToHash)).toString();
}
/**
* @param event The event to be tested.
* @return true if event is a path change notification.
*/
public boolean isPathChangeEvent(WatchEvent<?> event) {
return event.kind() == StandardWatchEventKinds.ENTRY_CREATE ||
event.kind() == StandardWatchEventKinds.ENTRY_MODIFY ||
event.kind() == StandardWatchEventKinds.ENTRY_DELETE;
}
public void copy(Path source, Path target, CopySourceMode sourceMode) throws IOException {
switch (sourceMode) {
case FILE:
Files.copy(
resolve(source),
resolve(target),
StandardCopyOption.REPLACE_EXISTING);
break;
case DIRECTORY_CONTENTS_ONLY:
MoreFiles.copyRecursively(resolve(source), resolve(target));
break;
case DIRECTORY_AND_CONTENTS:
MoreFiles.copyRecursively(resolve(source), resolve(target.resolve(source.getFileName())));
break;
}
}
public void move(Path source, Path target, CopyOption... options) throws IOException {
Files.move(resolve(source), resolve(target), options);
}
public void copyFolder(Path source, Path target) throws IOException {
copy(source, target, CopySourceMode.DIRECTORY_CONTENTS_ONLY);
}
public void copyFile(Path source, Path target) throws IOException {
copy(source, target, CopySourceMode.FILE);
}
public void createSymLink(Path sourcePath, Path targetPath, boolean force)
throws IOException {
if (force) {
Files.deleteIfExists(targetPath);
}
if (Platform.detect() == Platform.WINDOWS) {
if (isDirectory(sourcePath)) {
// Creating symlinks to directories on Windows requires escalated privileges. We're just
// going to have to copy things recursively.
MoreFiles.copyRecursively(sourcePath, targetPath);
} else {
Files.createLink(targetPath, sourcePath);
}
} else {
Files.createSymbolicLink(targetPath, sourcePath);
}
}
/**
* Returns true if the file under {@code path} exists and is a symbolic
* link, false otherwise.
*/
public boolean isSymLink(Path path) throws IOException {
return Files.isSymbolicLink(getPathForRelativePath(path));
}
/**
* Takes a sequence of paths relative to the project root and writes a zip file to {@code out}
* with the contents and structure that matches that of the specified paths.
*/
public void createZip(Collection<Path> pathsToIncludeInZip, File out) throws IOException {
createZip(pathsToIncludeInZip, out, ImmutableMap.<Path, String>of());
}
/**
* Similar to {@link #createZip(Collection, File)}, but also takes a list of additional files to
* write in the zip, including their contents, as a map.
*/
public void createZip(
Collection<Path> pathsToIncludeInZip,
File out,
ImmutableMap<Path, String> additionalFileContents) throws IOException {
Preconditions.checkState(!Iterables.isEmpty(pathsToIncludeInZip));
try (CustomZipOutputStream zip = ZipOutputStreams.newOutputStream(out)) {
for (Path path : pathsToIncludeInZip) {
Path full = getPathForRelativePath(path);
File file = full.toFile();
boolean isDirectory = isDirectory(full);
String entryName = path.toString();
if (isDirectory) {
entryName += "/";
}
CustomZipEntry entry = new CustomZipEntry(entryName);
// Support executable files. If we detect this file is executable, store this
// information as 0100 in the field typically used in zip implementations for
// POSIX file permissions. We'll use this information when unzipping.
if (file.canExecute()) {
entry.setExternalAttributes(
MorePosixFilePermissions.toMode(
EnumSet.of(PosixFilePermission.OWNER_EXECUTE)) << 16);
}
zip.putNextEntry(entry);
if (!isDirectory) {
try (InputStream input = Files.newInputStream(getPathForRelativePath(path))) {
ByteStreams.copy(input, zip);
}
}
zip.closeEntry();
}
for (Map.Entry<Path, String> fileContentsEntry : additionalFileContents.entrySet()) {
CustomZipEntry entry = new CustomZipEntry(fileContentsEntry.getKey().toString());
zip.putNextEntry(entry);
try (InputStream stream =
new ByteArrayInputStream(fileContentsEntry.getValue().getBytes(Charsets.UTF_8))) {
ByteStreams.copy(stream, zip);
}
zip.closeEntry();
}
}
}
/**
*
* @param event the event to format.
* @return the formatted event context string.
*/
public String createContextString(WatchEvent<?> event) {
if (isPathChangeEvent(event)) {
Path path = (Path) event.context();
return path.toAbsolutePath().normalize().toString();
}
return String.valueOf(event.context());
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ProjectFilesystem)) {
return false;
}
ProjectFilesystem that = (ProjectFilesystem) other;
return Objects.equals(projectRoot, that.projectRoot) &&
Objects.equals(ignorePaths, that.ignorePaths);
}
@Override
public int hashCode() {
return Objects.hash(projectRoot, ignorePaths);
}
/**
* @param path the path to check.
* @return whether ignoredPaths contains path or any of its ancestors.
*/
public boolean isIgnored(Path path) {
for (Path ignoredPath : getIgnorePaths()) {
if (path.startsWith(ignoredPath)) {
return true;
}
}
return false;
}
public Path createTempFile(
Path directory,
String prefix,
String suffix,
FileAttribute<?>... attrs)
throws IOException {
return Files.createTempFile(directory, prefix, suffix, attrs);
}
}