RecursiveFiles.java
/*-
* #%L
* io.earcam.instrumental.io.file
* %%
* Copyright (C) 2018 earcam
* %%
* SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
*
* You <b>must</b> choose to accept, in full - any individual or combination of
* the following licenses:
* <ul>
* <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
* <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
* <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
* <li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
* </ul>
* #L%
*/
package io.earcam.utilitarian.io.file;
import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.EnumSet;
/**
* Wise to invoke these methods with {@link LinkOption#NOFOLLOW_LINKS}
*/
public final class RecursiveFiles {
private abstract static class AbstractVisitor extends SimpleFileVisitor<Path> {
final Path sink;
final Path source;
final CopyOption[] options;
Path sinkSub;
public AbstractVisitor(Path source, Path sink, CopyOption... options)
{
this.source = source;
this.sink = sink;
this.options = options;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
{
sinkSub = sink.resolve(source.relativize(dir));
sinkSub.toFile().mkdirs();
return CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path directory, IOException thrown) throws IOException
{
super.postVisitDirectory(directory, thrown);
return postVisitDirectory(directory);
}
protected abstract FileVisitResult postVisitDirectory(Path directory) throws IOException;
@Override
public abstract FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException;
}
private static final class DeleteVisitor extends AbstractVisitor {
public DeleteVisitor(Path source, CopyOption... options)
{
super(source, source, options);
}
@Override
public FileVisitResult postVisitDirectory(Path directory) throws IOException
{
Files.delete(directory);
return CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
{
Files.delete(file);
return CONTINUE;
}
}
private static final class MoveVisitor extends AbstractVisitor {
public MoveVisitor(Path source, Path sink, CopyOption... options)
{
super(source, sink, options);
}
@Override
protected FileVisitResult postVisitDirectory(Path directory) throws IOException
{
Files.delete(directory);
return CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
{
Files.move(file, sinkSub.resolve(file.getFileName()), options);
return CONTINUE;
}
}
private static final class CopyVisitor extends AbstractVisitor {
public CopyVisitor(Path source, Path sink, CopyOption... options)
{
super(source, sink, options);
}
@Override
protected FileVisitResult postVisitDirectory(Path directory)
{
return CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
{
Files.copy(file, sinkSub.resolve(file.getFileName()), options);
return CONTINUE;
}
}
private RecursiveFiles()
{}
/**
* Wise to invoke this with {@link LinkOption#NOFOLLOW_LINKS}
*
* @param path
* @param options
* @throws IOException
*/
public static void delete(Path path, LinkOption... options) throws IOException
{
recurse(path, new DeleteVisitor(path, options));
}
/**
* Wise to invoke this with {@link LinkOption#NOFOLLOW_LINKS}
*
* @param source
* @param sink
* @param options
* @throws IOException
*/
public static void move(Path source, Path sink, CopyOption... options) throws IOException
{
recurse(source, new MoveVisitor(source, sink, options));
}
private static void recurse(Path source, AbstractVisitor visitor) throws IOException
{
boolean noFollow = Arrays.asList(visitor.options).contains(NOFOLLOW_LINKS);
EnumSet<FileVisitOption> options = noFollow ? EnumSet.noneOf(FileVisitOption.class) : EnumSet.of(FOLLOW_LINKS);
Files.walkFileTree(source, options, Integer.MAX_VALUE, visitor);
}
/**
* Wise to invoke this with {@link LinkOption#NOFOLLOW_LINKS}
*
* @param source
* @param sink
* @param options
* @throws IOException
*/
public static void copy(Path source, Path sink, CopyOption... options) throws IOException
{
recurse(source, new CopyVisitor(source, sink, options));
}
}