net.java.truevfs.access.TBIO Maven / Gradle / Ivy
Show all versions of truevfs-access Show documentation
/*
* Copyright (C) 2005-2015 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package net.java.truevfs.access;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystemException;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.util.Arrays;
import javax.annotation.CheckForNull;
import javax.annotation.concurrent.Immutable;
import net.java.truecommons.shed.BitField;
import net.java.truecommons.shed.Paths;
import net.java.truevfs.kernel.spec.FsAccessOption;
import static net.java.truevfs.kernel.spec.FsAccessOption.CREATE_PARENTS;
import net.java.truevfs.kernel.spec.FsNodePath;
import net.java.truecommons.cio.Entry;
import net.java.truecommons.cio.InputSocket;
import net.java.truecommons.cio.IoSockets;
import net.java.truecommons.cio.OutputSocket;
/**
* Provides fast bulk I/O operations for {@link File}s and {@link TFile}s.
*
* Note that in contrast to the {@link TFile} class, the methods in this
* class accept plain old {@link File} objects.
* However, full advantage is taken if a parameter is a {@link TFile} object.
*
* @author Christian Schlichtherle
*/
@Immutable
final class TBIO {
/**
* Moves the source file or directory tree to the destination file or
* directory tree by performing a recursive cp-then-delete operation.
* Hence, this file system operation works even with archive files or
* entries within archive files, but is not atomic.
*
* @param src the source directory tree or file.
* This file system entity needs to exist.
* @param dst the destination directory tree or file.
* This file systeme entity may or may not exist.
* If it does, its contents are overwritten.
* @param detector the object used to detect any archive files in the
* source and destination paths.
* @throws IOException if the source path contains the destination path
* or an elementary operation fails for any reason.
*/
static void
mv(final File src, final File dst, final TArchiveDetector detector)
throws IOException {
checkContains(src, dst);
if (dst.exists())
throw new FileAlreadyExistsException(src.getPath(), dst.getPath(), null);
mv0(src, dst, detector);
}
/** Unchecked parameters version. */
private static void
mv0(final File src, final File dst, final TArchiveDetector detector)
throws IOException {
if (src.isDirectory()) {
final long srcLastModified = src.lastModified();
final boolean srcIsArchived = src instanceof TFile
&& null != ((TFile) src).getInnerArchive();
final boolean dstIsArchived = dst instanceof TFile
&& null != ((TFile) dst).getInnerArchive();
final boolean srcIsGhost = srcIsArchived
&& 0 >= srcLastModified;
if (!srcIsGhost || !dstIsArchived || !TConfig.current().isLenient())
if (!dst.mkdir() && !dst.isDirectory())
throw new NotDirectoryException(dst.getPath());
final String[] members = src.list();
if (null == members)
throw new FileSystemException(dst.getPath(), null, "Cannot list directory!");
if (!srcIsArchived && dstIsArchived) {
// Create sorted entries if writing a new archive file.
// This is courtesy only, so natural order is sufficient.
Arrays.sort(members);
}
for (final String member : members)
mv0( new TFile(src, member, detector),
new TFile(dst, member, detector),
detector);
if (!srcIsGhost)
if (!dst.setLastModified(srcLastModified))
throw new FileSystemException(dst.getPath(), null, "Cannot set last modification time!");
} else if (src.isFile()) {
if (dst.exists() && !dst.isFile())
throw new FileSystemException(dst.getPath(), null, "Not a file!");
cp0(true, src, dst);
} else if (src.exists()) {
throw new FileSystemException(src.getPath(), null, "Cannot move special file!");
} else {
throw new NoSuchFileException(src.getPath());
}
if (!src.delete())
throw new FileSystemException(src.getPath(), null, "Cannot delete!");
}
/**
* Recursively copies the source directory tree or file to the destination
* directory tree or file.
*
* @param preserve If {@code true}, then a best effort approach is used to
* copy as much properties of any source files to the destination
* files as possible.
* Which attributes are actually copied is specific to the
* destination file system driver implementation, but the minimum
* guarantee is to copy the last modification time.
* @param src the source directory tree or file.
* This file system entity needs to exist.
* @param dst the destination directory tree or file.
* This file systeme entity may or may not exist.
* If it does, its contents are overwritten.
* @param srcDetector the object used to detect any archive files in the
* source path.
* @param dstDetector the object used to detect any archive files in the
* destination path.
* @throws IOException if the source path contains the destination path
* or any I/O error.
*/
static void
cp_r( final boolean preserve,
final File src,
final File dst,
final TArchiveDetector srcDetector,
final TArchiveDetector dstDetector)
throws IOException {
checkContains(src, dst);
cp_r0(preserve, src, dst, srcDetector, dstDetector);
}
/** Unchecked parameters version. */
private static void
cp_r0( final boolean preserve,
final File src,
final File dst,
final TArchiveDetector srcDetector,
final TArchiveDetector dstDetector)
throws IOException {
if (src.isDirectory()) {
final long srcLastModified = src.lastModified();
final boolean srcArchived = src instanceof TFile
&& null != ((TFile) src).getInnerArchive();
final boolean dstArchived = dst instanceof TFile
&& null != ((TFile) dst).getInnerArchive();
final boolean srcIsGhost = srcArchived && 0 >= srcLastModified;
if (!srcIsGhost || !dstArchived || !TConfig.current().isLenient())
if (!dst.mkdir() && !dst.isDirectory())
throw new NotDirectoryException(dst.getPath());
final String[] members = src.list();
if (null == members)
throw new FileSystemException(dst.getPath(), null, "Cannot list directory!");
if (!srcArchived && dstArchived) {
// Create sorted entries if copying an ordinary directory to a
// new archive.
// This is a courtesy only, so natural order is sufficient.
Arrays.sort(members);
}
for (final String member : members)
cp_r0( preserve,
new TFile(src, member, srcDetector),
new TFile(dst, member, dstDetector),
srcDetector, dstDetector);
if (preserve && !srcIsGhost)
if (!dst.setLastModified(srcLastModified))
throw new FileSystemException(dst.getPath(), null, "Cannot set last modification time!");
} else if (src.isFile()) {
if (dst.exists() && !dst.isFile())
throw new FileSystemException(dst.getPath(), null, "Not a file!");
cp0(preserve, src, dst);
} else if (src.exists()) {
throw new FileSystemException(src.getPath(), null, "Cannot copy special file!");
} else {
throw new NoSuchFileException(src.getPath());
}
}
/**
* Copies a single source file to a destination file.
*
* @param preserve if an elementary cp operation shall cp as much
* properties of the source file to the destination file, too.
* Currently, only the last modification time is preserved.
* Note that this property set may get extended over time.
* @param src the source file.
* This file system entity needs to exist.
* @param dst the destination file.
* This file systeme entity may or may not exist.
* If it does, its contents are overwritten.
* @throws IOException if the source path contains the destination path
* or an elementary operation fails for any reason.
*/
static void
cp(final boolean preserve, final File src, final File dst)
throws IOException {
checkContains(src, dst);
cp0(preserve, src, dst);
}
/** Unchecked parameters version. */
private static void
cp0(final boolean preserve, final File src, final File dst)
throws IOException {
final BitField preferences =
TConfig.current().getAccessPreferences();
final InputSocket> input = input(preferences, src);
final OutputSocket> output = output(preferences, dst,
preserve ? input.target() : null);
IoSockets.copy(input, output);
}
/**
* Recursively deletes the given file or directory tree.
*
* @param file the file or directory tree to delete recursively.
* @throws IOException if an elementary operation fails for any reason.
*/
static void rm_r(final File file, final TArchiveDetector detector)
throws IOException {
if (file.isDirectory()) {
final String[] members = file.list();
if (null == members)
throw new FileSystemException(file.getPath(), null, "Cannot list directory!");
for (final String member : members)
rm_r(new TFile(file, member, detector), detector);
}
TFile.rm(file);
}
/**
* Throws an {@code IOException} if and only if the path represented by
* {@code a} contains the path represented by {@code b}, where a path is
* said to contain another path if and only if it is equal or an ancestor
* of the other path.
*
* Note that this method uses the absolute path of both files as if by
* calling {@link File#getAbsolutePath()}.
*
* @param a a file.
* @param b another file.
*/
private static void checkContains(File a, File b) throws IOException {
if (Paths.contains( a.getAbsolutePath(),
b.getAbsolutePath(),
File.separatorChar))
throw new FileSystemException(a.getPath(), b.getPath(), "First path contains second path!");
}
private static TArchiveDetector detector(File file) {
return file instanceof TFile
? ((TFile) file).getArchiveDetector()
: TConfig.current().getArchiveDetector();
}
/**
* Returns an input socket for the given file.
*
* @param file the file to read.
* @param options the options for accessing the file.
* @return An input socket for the given file.
*/
static InputSocket> input(
final BitField options,
final File file) {
if (file instanceof TFile) {
final TFile tfile= (TFile) file;
final TFile archive = tfile.getInnerArchive();
if (null != archive)
return archive
.getController()
.input(options, tfile.getNodeName());
}
final FsNodePath path = new FsNodePath(file);
return TConfig
.current()
.getManager()
.controller(detector(file), path.getMountPoint())
.input(options, path.getNodeName());
}
/**
* Returns an output socket for the given file.
*
* @param file the file to write.
* @param options the options for accessing the file.
* @param template a nullable template from which file attributes shall
* get copied.
* @return An output socket for the given file.
*/
static OutputSocket> output(
final BitField options,
final File file,
final @CheckForNull Entry template) {
if (file instanceof TFile) {
final TFile tfile = (TFile) file;
final TFile archive = tfile.getInnerArchive();
if (null != archive)
return archive
.getController()
.output( options,
tfile.getNodeName(),
template);
}
final FsNodePath path = new FsNodePath(file);
return TConfig
.current()
.getManager()
.controller(detector(file), path.getMountPoint())
.output( options.clear(CREATE_PARENTS),
path.getNodeName(),
template);
}
private TBIO() { }
}