/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2015 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.exist.util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.evolvedbinary.j8fu.Either;
import com.evolvedbinary.j8fu.function.FunctionE;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Adam Retter <adam.retter@googlemail.com>
* @author alex
*/
public class FileUtils {
private final static Logger LOG = LogManager.getLogger(FileUtils.class);
/**
* Convert an array of {@link java.io.File}
* to an array of {@link java.nio.file.Path}
*
* @param files An array of {@link java.io.File}
*
* @return If files is null or empty, then
* an empty array, otherwise an array of corresponding
* {@link java.nio.file.Path}
*/
public static Path[] asPaths(final File[] files) {
return asPathsList(files).toArray(new Path[files.length]);
}
/**
* Convert an array of {@link java.io.File}
* to a List of {@link java.nio.file.Path}
*
* @param files An array of {@link java.io.File}
*
* @return If files is null or empty, then
* an empty list, otherwise a list of corresponding
* {@link java.nio.file.Path}
*/
public static List<Path> asPathsList(final File[] files) {
return Optional.ofNullable(files)
.map(fs -> Arrays.stream(fs).map(File::toPath).collect(Collectors.toList()))
.orElse(Collections.EMPTY_LIST);
}
/**
* Deletes a path from the filesystem
*
* If the path is a directory its contents
* will be recursively deleted before it itself
* is deleted.
*
* Note that removal of a directory is not an atomic-operation
* and so if an error occurs during removal, some of the directories
* descendants may have already been removed
*
* @throws IOException if an error occurs whilst removing a file or directory
*/
public static void delete(final Path path) throws IOException {
if (!Files.isDirectory(path)) {
Files.deleteIfExists(path);
} else {
Files.walkFileTree(path, deleteDirVisitor);
}
}
/**
* Deletes a path from the filesystem
*
* If the path is a directory its contents
* will be recursively deleted before it itself
* is deleted
*
* This method will never throw an IOException, it
* instead returns `false` if an error occurs
* whilst removing a file or directory
*
* Note that removal of a directory is not an atomic-operation
* and so if an error occurs during removal, some of the directories
* descendants may have already been removed
*
* @return false if an error occurred, true otherwise
*/
public static boolean deleteQuietly(final Path path) {
try {
if (!Files.isDirectory(path)) {
return Files.deleteIfExists(path);
} else {
Files.walkFileTree(path, deleteDirVisitor);
}
return true;
} catch (final IOException ioe) {
LOG.error("Unable to delete: " + path.toAbsolutePath().toString(), ioe);
return false;
}
}
private final static SimpleFileVisitor<Path> deleteDirVisitor = new DeleteDirVisitor();
private static class DeleteDirVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
Files.deleteIfExists(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
if (exc != null) {
throw exc;
}
Files.deleteIfExists(dir);
return FileVisitResult.CONTINUE;
}
}
/**
* Determine the size of a collection of files and/or directories
*
* @return The size of the files and directories, or -1 if the file size cannot be determined
*/
public static long sizeQuietly(final Collection<Path> paths) {
return paths.stream().map(FileUtils::sizeQuietly)
.filter(size -> size != -1)
.reduce((a, b) -> a + b)
.orElse(-1l);
}
/**
* Determine the size of a file or directory
*
* @return The size of the file or directory, or -1 if the file size cannot be determined
*/
public static long sizeQuietly(final Path path) {
try {
if(!Files.isDirectory(path)) {
return Files.size(path);
} else {
final DirSizeVisitor dirSizeVisitor = new DirSizeVisitor();
Files.walkFileTree(path, dirSizeVisitor);
return dirSizeVisitor.totalSize();
}
} catch(final IOException ioe) {
LOG.error("Unable to determine size of: " + path.toString(), ioe);
return -1;
}
}
/**
* Make a measurement of the FileStore for a particular path
*
* @param path The path for which the FileStore should be measured
* @param measurer A function which provided with a FileStore makes a measurement
*
* @return The measured value or -1 if an error occurred whilst accessing the FileStore
*/
public static long measureFileStore(final Path path, final FunctionE<FileStore, Long, IOException> measurer) {
try {
return measurer.apply(Files.getFileStore(path));
} catch(final IOException ioe) {
LOG.error(ioe);
return -1l;
}
}
/**
* Determine the last modified time of a file or directory
*
* @return Eitehr an IOException or the last modified time of the file or directory
*/
public static Either<IOException, FileTime> lastModifiedQuietly(final Path path) {
try {
return Either.Right(Files.getLastModifiedTime(path));
} catch (final IOException e) {
return Either.Left(e);
}
}
private static class DirSizeVisitor extends SimpleFileVisitor<Path> {
private long size = 0;
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
size += Files.size(file);
return FileVisitResult.CONTINUE;
}
public long totalSize() {
return size;
}
}
/**
* Makes directories on the filesystem the filesystem
*
* This method will never throw an IOException, it
* instead returns `false` if an error occurs
* whilst creating a file or directory
*
* Note that creation of a directory is not an atomic-operation
* and so if an error occurs during creation, some of the directories
* descendants may have already been created
*
* @return false if an error occurred, true otherwise
*/
public static boolean mkdirsQuietly(final Path dir) {
try {
if (!Files.exists(dir)) {
Files.createDirectories(dir);
}
return true;
} catch (final IOException ioe) {
LOG.error("Unable to mkdirs: " + dir.toAbsolutePath().toString(), ioe);
return false;
}
}
/**
* Attempts to resolve the child
* against the parent.
*
* If there is no parent, then the child
* is resolved relative to the CWD
*
* @return The resolved path
*/
public static Path resolve(final Optional<Path> parent, final String child) {
return parent.map(p -> p.resolve(child)).orElse(Paths.get(child));
}
/**
* Get just the filename part of the path
*
* @return The filename
*/
public static String fileName(final Path path) {
return path.getFileName().toString();
}
/**
* A list of the entries in the directory. The listing is not recursive.
*
* @param directory The directory to list the entries for
*
* @return The list of entries
*/
public static List<Path> list(final Path directory) throws IOException {
try(final Stream<Path> entries = Files.list(directory)) {
return entries.collect(Collectors.toList());
}
}
/**
* A list of the entries in the directory. The listing is not recursive.
*
* @param directory The directory to list the entries for
* @param filter A filter to be applied to the list
* @return The list of entries
*/
public static List<Path> list(final Path directory, final Predicate<Path> filter) throws IOException {
try(final Stream<Path> entries = Files.list(directory).filter(filter)) {
return entries.collect(Collectors.toList());
}
}
/**
* @param path a path or uri
* @return the directory portion of a path by stripping the last '/' and
* anything following, unless the path has no '/', in which case '.' is returned,
* or ends with '/', in
* which case return the path unchanged.
*/
public static String dirname(final String path) {
final int islash = path.lastIndexOf('/');
if (islash >= 0 && islash < path.length() - 1) {
return path.substring(0, islash);
} else if (islash >= 0) {
return path;
} else {
return ".";
}
}
/**
* @param path1
* @param path2
* @return path1 + path2, joined by a single file separator (or /, if a slash is already present).
*/
public static String addPaths(final String path1, final String path2) {
if (path1.endsWith("/") || path2.endsWith(File.separator)) {
if (path2.startsWith("/") || path2.startsWith(File.separator)) {
return path1 + path2.substring(1);
} else {
return path1 + path2;
}
} else {
if (path2.startsWith("/") || path2.startsWith(File.separator)) {
return path1 + path2;
} else {
return path1 + File.separatorChar + path2;
}
}
}
}