package org.dcache.pool.repository;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.SyncFailedException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.util.EnumSet;
import java.util.Set;
import static java.nio.file.StandardOpenOption.*;
public class FileRepositoryChannel implements RepositoryChannel {
private static final FileAttribute<?>[] NO_ATTRIBUTES = new FileAttribute<?>[0];
private static final Set<StandardOpenOption> O_READ = EnumSet.of(READ);
private static final Set<StandardOpenOption> O_RW = EnumSet.of(READ, WRITE, CREATE);
private static final Set<StandardOpenOption> O_RWD = EnumSet.of(READ, WRITE, CREATE, DSYNC);
private static final Set<StandardOpenOption> O_RWS = EnumSet.of(READ, WRITE, CREATE, DSYNC, SYNC);
private final FileChannel _fileChannel;
private final Path _path;
/*
* Cached value of files size. If value is -1, then we have to get file size
* by querying the underlying file system.
*/
private final long _fileSize;
/**
* Creates a {@link RepositortyChannel} to read from, and optionally to
* write to, the file specified by the {@link File} argument.
*
* <a name="mode"><p> The <tt>mode</tt> argument specifies the access mode
* in which the file is to be opened. The permitted values and their
* meanings are:
*
* <blockquote><table summary="Access mode permitted values and meanings">
* <tr><th><p align="left">Value</p></th><th><p align="left">Meaning</p></th></tr>
* <tr><td valign="top"><tt>"r"</tt></td>
* <td> Open for reading only. Invoking any of the <tt>write</tt>
* methods of the resulting object will cause an {@link
* IOException} to be thrown. </td></tr>
* <tr><td valign="top"><tt>"rw"</tt></td>
* <td> Open for reading and writing. If the file does not already
* exist then an attempt will be made to create it. </td></tr>
* <tr><td valign="top"><tt>"rws"</tt></td>
* <td> Open for reading and writing, as with <tt>"rw"</tt>, and also
* require that every update to the file's content or metadata be
* written synchronously to the underlying storage device. </td></tr>
* <tr><td valign="top"><tt>"rwd" </tt></td>
* <td> Open for reading and writing, as with <tt>"rw"</tt>, and also
* require that every update to the file's content be written
* synchronously to the underlying storage device. </td></tr>
* </table></blockquote>
*
* The <tt>"rws"</tt> and <tt>"rwd"</tt> modes work much like the {@link
* FileChannel#force(boolean) force(boolean)} method of
* the {@link FileChannel} class, passing arguments of
* <tt>true</tt> and <tt>false</tt>, respectively, except that they always
* apply to every I/O operation and are therefore often more efficient. If
* the file resides on a local storage device then when an invocation of a
* method of this class returns it is guaranteed that all changes made to
* the file by that invocation will have been written to that device. This
* is useful for ensuring that critical information is not lost in the
* event of a system crash. If the file does not reside on a local device
* then no such guarantee is made.
*
* <p> The <tt>"rwd"</tt> mode can be used to reduce the number of I/O
* operations performed. Using <tt>"rwd"</tt> only requires updates to the
* file's content to be written to storage; using <tt>"rws"</tt> requires
* updates to both the file's content and its metadata to be written, which
* generally requires at least one more low-level I/O operation.
*
*
* @param file the file object
* @param mode the access mode, as described
* <a href="#mode">above</a>
* @exception IllegalArgumentException if the mode argument is not equal
* to one of <tt>"r"</tt>, <tt>"rw"</tt>, <tt>"rws"</tt>, or
* <tt>"rwd"</tt>
* @throws FileNotFoundException
* if the mode is <tt>"r"</tt> but the given file object does
* not denote an existing regular file, or if the mode begins
* with <tt>"rw"</tt> but the given file object does not denote
* an existing, writable regular file and a new regular file of
* that name cannot be created, or if some other error occurs
* while opening or creating the file
*/
public FileRepositoryChannel(Path path, String mode) throws FileNotFoundException, IOException {
_path = path;
Set<StandardOpenOption> openOptions = getOpenOptions(mode);
_fileChannel = FileChannel.open(path, openOptions, NO_ATTRIBUTES);
_fileSize = mode.equals("r") ? _fileChannel.size() : -1;
}
private Set<StandardOpenOption> getOpenOptions(String mode) throws IllegalArgumentException {
Set<StandardOpenOption> openOptions;
switch (mode) {
case "rws":
openOptions = O_RWS;
break;
case "rwd":
openOptions = O_RWD;
break;
case "rw":
openOptions = O_RW;
break;
case "r":
openOptions = O_READ;
break;
default:
throw new IllegalArgumentException("Illegal mode \"" + mode + "\"");
}
return openOptions;
}
@Override
public long position() throws IOException {
return _fileChannel.position();
}
@Override
public RepositoryChannel position(long position) throws IOException {
_fileChannel.position(position);
return this;
}
@Override
public long size() throws IOException {
return _fileSize == -1 ? _fileChannel.size() : _fileSize;
}
@Override
public void sync() throws SyncFailedException, IOException {
_fileChannel.force(false);
}
@Override
public RepositoryChannel truncate(long size) throws IOException {
_fileChannel.truncate(size);
return this;
}
@Override
public void close() throws IOException {
_fileChannel.close();
}
@Override
public boolean isOpen() {
return _fileChannel.isOpen();
}
@Override
public int read(ByteBuffer dst) throws IOException {
return _fileChannel.read(dst);
}
@Override
public int read(ByteBuffer buffer, long position) throws IOException {
return _fileChannel.read(buffer, position);
}
@Override
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
return _fileChannel.read(dsts, offset, length);
}
@Override
public long read(ByteBuffer[] dsts) throws IOException {
return _fileChannel.read(dsts);
}
@Override
public int write(ByteBuffer src) throws IOException {
return _fileChannel.write(src);
}
@Override
public int write(ByteBuffer buffer, long position) throws IOException {
return _fileChannel.write(buffer, position);
}
@Override
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
return _fileChannel.write(srcs, offset, length);
}
@Override
public long write(ByteBuffer[] srcs) throws IOException {
return _fileChannel.write(srcs);
}
@Override
public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
return _fileChannel.transferTo(position, count, target);
}
@Override
public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
return _fileChannel.transferFrom(src, position, count);
}
}