co.paralleluniverse.fibers.io.FiberFileChannel Maven / Gradle / Ivy
/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.fibers.io;
import co.paralleluniverse.common.util.CheckedCallable;
import co.paralleluniverse.fibers.Suspendable;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.*;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.spi.FileSystemProvider;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* A fiber-blocking version of {@link FileChannel}.
*
* @author pron
*/
public class FiberFileChannel implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel {
private static final ExecutorService fiberFileThreadPool = Executors.newCachedThreadPool(
new ThreadFactoryBuilder().setDaemon(true).setNameFormat("fiber-file-open-%d").build());
private static final FileAttribute>[] NO_ATTRIBUTES = new FileAttribute[0];
private final AsynchronousFileChannel ac;
private long position;
FiberFileChannel(AsynchronousFileChannel afc) {
ac = afc;
}
/**
* Opens or creates a file, returning a file channel to access the file.
*
* The {@code options} parameter determines how the file is opened.
* The {@link StandardOpenOption#READ READ} and {@link StandardOpenOption#WRITE
* WRITE} options determine if the file should be opened for reading and/or
* writing. If neither option (or the {@link StandardOpenOption#APPEND APPEND}
* option) is contained in the array then the file is opened for reading.
* By default reading or writing commences at the beginning of the file.
*
*
In the addition to {@code READ} and {@code WRITE}, the following
* options may be present:
*
*
* Option Description
*
*
*
* {@link StandardOpenOption#APPEND APPEND}
* If this option is present then the file is opened for writing and
* each invocation of the channel's {@code write} method first advances
* the position to the end of the file and then writes the requested
* data. Whether the advancement of the position and the writing of the
* data are done in a single atomic operation is system-dependent and
* therefore unspecified. This option may not be used in conjunction
* with the {@code READ} or {@code TRUNCATE_EXISTING} options.
*
*
* {@link StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING}
* If this option is present then the existing file is truncated to
* a size of 0 bytes. This option is ignored when the file is opened only
* for reading.
*
*
* {@link StandardOpenOption#CREATE_NEW CREATE_NEW}
* If this option is present then a new file is created, failing if
* the file already exists. When creating a file the check for the
* existence of the file and the creation of the file if it does not exist
* is atomic with respect to other file system operations. This option is
* ignored when the file is opened only for reading.
*
*
* {@link StandardOpenOption#CREATE CREATE}
* If this option is present then an existing file is opened if it
* exists, otherwise a new file is created. When creating a file the check
* for the existence of the file and the creation of the file if it does
* not exist is atomic with respect to other file system operations. This
* option is ignored if the {@code CREATE_NEW} option is also present or
* the file is opened only for reading.
*
*
* {@link StandardOpenOption#DELETE_ON_CLOSE DELETE_ON_CLOSE}
* When this option is present then the implementation makes a
* best effort attempt to delete the file when closed by
* the {@link #close close} method. If the {@code close} method is not
* invoked then a best effort attempt is made to delete the file
* when the Java virtual machine terminates.
*
*
* {@link StandardOpenOption#SPARSE SPARSE}
* When creating a new file this option is a hint that the
* new file will be sparse. This option is ignored when not creating
* a new file.
*
*
* {@link StandardOpenOption#SYNC SYNC}
* Requires that every update to the file's content or metadata be
* written synchronously to the underlying storage device. (see Synchronized I/O file
* integrity).
*
*
* {@link StandardOpenOption#DSYNC DSYNC}
* Requires that every update to the file's content be written
* synchronously to the underlying storage device. (see Synchronized I/O file
* integrity).
*
*
*
*
*
*
* An implementation may also support additional options.
*
*
The {@code attrs} parameter is an optional array of file {@link
* FileAttribute file-attributes} to set atomically when creating the file.
*
*
The new channel is created by invoking the {@link
* FileSystemProvider#newFileChannel newFileChannel} method on the
* provider that created the {@code Path}.
*
* @param path The path of the file to open or create
* @param options Options specifying how the file is opened
* @param ioExecutor The thread pool or {@code null} to associate the channel with the default thread pool
* @param attrs An optional list of file attributes to set atomically when creating the file
*
* @return A new file channel
*
* @throws IllegalArgumentException If the set contains an invalid combination of options
* @throws UnsupportedOperationException If the {@code file} is associated with a provider that does not
* support creating asynchronous file channels, or an unsupported
* open option is specified, or the array contains an attribute that
* cannot be set atomically when creating the file
* @throws IOException If an I/O error occurs
* @throws SecurityException If a security manager is installed and it denies an
* unspecified permission required by the implementation.
* In the case of the default provider, the {@link SecurityManager#checkRead(String)}
* method is invoked to check read access if the file is opened for reading.
* The {@link SecurityManager#checkWrite(String)} method is invoked to check
* write access if the file is opened for writing
*/
@Suspendable
public static FiberFileChannel open(final ExecutorService ioExecutor, final Path path, final Set extends OpenOption> options, final FileAttribute>... attrs) throws IOException {
final ExecutorService ioExec = ioExecutor != null ? ioExecutor : fiberFileThreadPool; // FiberAsyncIO.ioExecutor(); //
AsynchronousFileChannel afc = FiberAsyncIO.runBlockingIO(fiberFileThreadPool, new CheckedCallable() {
@Override
public AsynchronousFileChannel call() throws IOException {
return AsynchronousFileChannel.open(path, options, ioExec, attrs);
}
});
return new FiberFileChannel(afc);
}
/**
* Opens or creates a file for reading and/or writing, returning a file channel to access the file.
*
*
* An invocation of this method behaves in exactly the same way as the
* invocation
*
* ch.{@link #open(ExecutorService,Path,Set,FileAttribute[])
* open}(null, file, opts, new FileAttribute<?>[0]);
*
* where {@code opts} is a {@code Set} containing the options specified to
* this method.
*
*
* The resulting channel is associated with default thread pool to which
* tasks are submitted to handle I/O events and dispatch to completion
* handlers that consume the result of asynchronous operations performed on
* the resulting channel.
*
* @param path The path of the file to open or create
* @param options Options specifying how the file is opened
*
* @return A new file channel
*
* @throws IllegalArgumentException If the set contains an invalid combination of options
* @throws UnsupportedOperationException If the {@code file} is associated with a provider that does not
* support creating file channels, or an unsupported open option is
* specified
* @throws IOException If an I/O error occurs
* @throws SecurityException If a security manager is installed and it denies an
* unspecified permission required by the implementation.
* In the case of the default provider, the {@link SecurityManager#checkRead(String)}
* method is invoked to check read access if the file is opened for reading.
* The {@link SecurityManager#checkWrite(String)} method is invoked to check
* write access if the file is opened for writing
*/
@Suspendable
public static FiberFileChannel open(Path path, OpenOption... options) throws IOException {
Set set = new HashSet(options.length);
Collections.addAll(set, options);
return open(null, path, set, NO_ATTRIBUTES);
}
@Override
public final boolean isOpen() {
return ac.isOpen();
}
@Override
@Suspendable
public void close() throws IOException {
ac.close();
FiberAsyncIO.runBlockingIO(fiberFileThreadPool, new CheckedCallable() {
@Override
public Void call() throws IOException {
ac.close();
return null;
}
});
}
@Override
public long position() throws IOException {
return position;
}
@Override
public FiberFileChannel position(long newPosition) throws IOException {
this.position = newPosition;
return this;
}
/**
* Reads a sequence of bytes from this channel into the given buffer,
* starting at the given file position.
*
*
* This method works in the same manner as the {@link
* #read(ByteBuffer)} method, except that bytes are read starting at the
* given file position rather than at the channel's current position. This
* method does not modify this channel's position. If the given position
* is greater than the file's current size then no bytes are read.
*
* @param dst
* The buffer into which bytes are to be transferred
*
* @param position
* The file position at which the transfer is to begin;
* must be non-negative
*
* @return The number of bytes read, possibly zero, or {@code -1} if the
* given position is greater than or equal to the file's current
* size
*
* @throws IllegalArgumentException
* If the position is negative
*
* @throws NonReadableChannelException
* If this channel was not opened for reading
*
* @throws ClosedChannelException
* If this channel is closed
*
* @throws AsynchronousCloseException
* If another thread closes this channel
* while the read operation is in progress
*
* @throws ClosedByInterruptException
* If another thread interrupts the current thread
* while the read operation is in progress, thereby
* closing the channel and setting the current thread's
* interrupt status
*
* @throws IOException
* If some other I/O error occurs
*/
@Suspendable
public int read(final ByteBuffer dst, final long position) throws IOException {
return new FiberAsyncIO() {
@Override
protected void requestAsync() {
ac.read(dst, position, null, makeCallback());
}
}.runSneaky();
}
@Override
@Suspendable
public int read(ByteBuffer dst) throws IOException {
final int bytes = read(dst, position);
position(position + bytes);
return bytes;
}
/**
* Reads a sequence of bytes from this channel into the given buffers.
*
*
* Bytes are read starting at this channel's current file position, and
* then the file position is updated with the number of bytes actually
* read. Otherwise this method behaves exactly as specified in the {@link
* ScatteringByteChannel} interface.
*/
@Override
@Suspendable
public final long read(ByteBuffer[] dsts) throws IOException {
return read(dsts, 0, dsts.length);
}
@Override
@Suspendable
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
long r = 0;
for (int i = 0; i < length; i++)
r += read(dsts[offset + i]);
return r;
}
/**
* Writes a sequence of bytes to this channel from the given buffer,
* starting at the given file position.
*
*
* This method works in the same manner as the {@link
* #write(ByteBuffer)} method, except that bytes are written starting at
* the given file position rather than at the channel's current position.
* This method does not modify this channel's position. If the given
* position is greater than the file's current size then the file will be
* grown to accommodate the new bytes; the values of any bytes between the
* previous end-of-file and the newly-written bytes are unspecified.
*
* @param src
* The buffer from which bytes are to be transferred
*
* @param position
* The file position at which the transfer is to begin;
* must be non-negative
*
* @return The number of bytes written, possibly zero
*
* @throws IllegalArgumentException
* If the position is negative
*
* @throws NonWritableChannelException
* If this channel was not opened for writing
*
* @throws ClosedChannelException
* If this channel is closed
*
* @throws AsynchronousCloseException
* If another thread closes this channel
* while the write operation is in progress
*
* @throws ClosedByInterruptException
* If another thread interrupts the current thread
* while the write operation is in progress, thereby
* closing the channel and setting the current thread's
* interrupt status
*
* @throws IOException
* If some other I/O error occurs
*/
@Suspendable
public int write(final ByteBuffer src, final long position) throws IOException {
return new FiberAsyncIO() {
@Override
protected void requestAsync() {
ac.write(src, position, null, makeCallback());
}
}.runSneaky();
}
@Override
@Suspendable
public int write(ByteBuffer src) throws IOException {
final int bytes = write(src, position);
position(position + bytes);
return bytes;
}
@Override
@Suspendable
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
long r = 0;
for (int i = 0; i < length; i++)
r += write(srcs[offset + i]);
return r;
}
@Override
@Suspendable
public final long write(ByteBuffer[] srcs) throws IOException {
return write(srcs, 0, srcs.length);
}
@Override
public long size() throws IOException {
return ac.size();
}
@Suspendable
public FileLock lock(final long position, final long size, final boolean shared) throws IOException {
return new FiberAsyncIO() {
@Override
protected void requestAsync() {
ac.lock(position, size, shared, null, makeCallback());
}
}.runSneaky();
}
public void force(boolean metaData) throws IOException {
ac.force(metaData);
}
@Override
public FiberFileChannel truncate(long size) throws IOException {
ac.truncate(size);
return this;
}
public FileLock tryLock(long position, long size, boolean shared) throws IOException {
return ac.tryLock(position, size, shared);
}
public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
throw new UnsupportedOperationException();
}
public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
throw new UnsupportedOperationException();
}
public MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) throws IOException {
throw new UnsupportedOperationException();
}
}