blob: 9a552352803b2bd466934fe16f8976bf1e1f2007 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.jimfs.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndexes;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
import com.google.jimfs.internal.file.ByteStore;
import com.google.jimfs.internal.file.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.OpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
/**
* A {@link FileChannel} implementation that reads and writes to a {@link ByteStore} object. The
* read and write methods and other methods that read or change the position of the channel are
* synchronized because the {@link ReadableByteChannel} and {@link WritableByteChannel} interfaces
* specify that the read and write methods block when another thread is currently doing a read or
* write operation.
*
* @author Colin Decker
*/
final class JimfsFileChannel extends FileChannel {
private volatile File file;
private volatile ByteStore store;
private final boolean readable;
private final boolean writable;
private final boolean append;
private int position;
public JimfsFileChannel(File file, Set<? extends OpenOption> options) {
this.file = file;
this.store = file.content();
this.readable = options.contains(READ);
this.writable = options.contains(WRITE);
this.append = options.contains(APPEND);
}
/**
* Returns an {@link InputStream} view of this channel.
*/
public InputStream asInputStream() {
checkReadable();
return Channels.newInputStream(this);
}
/**
* Returns an {@link OutputStream} view of this channel.
*/
public OutputStream asOutputStream() {
checkWritable();
return Channels.newOutputStream(this);
}
/**
* Returns an {@link AsynchronousFileChannel} view of this channel using the given executor for
* asynchronous operations.
*/
public AsynchronousFileChannel asAsynchronousFileChannel(ExecutorService executor) {
return new JimfsAsynchronousFileChannel(this, executor);
}
void checkReadable() {
if (!readable) {
throw new NonReadableChannelException();
}
}
void checkWritable() {
if (!writable) {
throw new NonWritableChannelException();
}
}
void checkOpen() throws ClosedChannelException {
if (store == null) {
throw new ClosedChannelException();
}
}
@Override
public synchronized int read(ByteBuffer dst) throws IOException {
checkOpen();
checkReadable();
file.updateAccessTime();
int read = store.read(position, dst);
if (read != -1) {
position += read;
}
return read;
}
@Override
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
checkPositionIndexes(offset, offset + length, dsts.length);
return read(Arrays.asList(dsts).subList(offset, offset + length));
}
private synchronized int read(List<ByteBuffer> buffers) throws IOException {
checkOpen();
checkReadable();
file.updateAccessTime();
int read = store.read(position, buffers);
if (read != -1) {
position += read;
}
return read;
}
@Override
public synchronized int write(ByteBuffer src) throws IOException {
checkOpen();
checkWritable();
file.updateModifiedTime();
int written;
if (append) {
written = store.append(src);
position = store.sizeInBytes();
} else {
written = store.write(position, src);
position += written;
}
return written;
}
@Override
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
checkPositionIndexes(offset, offset + length, srcs.length);
return write(Arrays.asList(srcs).subList(offset, offset + length));
}
private synchronized int write(List<ByteBuffer> srcs) throws IOException {
checkOpen();
checkWritable();
file.updateModifiedTime();
int written;
if (append) {
written = store.append(srcs);
position = store.sizeInBytes();
} else {
written = store.write(position, srcs);
position += written;
}
return written;
}
@Override
public synchronized long position() throws IOException {
checkOpen();
return position;
}
@Override
public synchronized FileChannel position(long newPosition) throws IOException {
checkNotNegative(newPosition, "newPosition");
checkOpen();
this.position = (int) newPosition;
return this;
}
@Override
public synchronized long size() throws IOException {
checkOpen();
return store.sizeInBytes();
}
@Override
public synchronized FileChannel truncate(long size) throws IOException {
checkNotNegative(size, "size");
checkOpen();
checkWritable();
file.updateModifiedTime();
store.truncate((int) size);
if (position > size) {
position = (int) size;
}
return this;
}
@Override
public synchronized void force(boolean metaData) throws IOException {
checkOpen();
// do nothing... writes are all synchronous anyway
}
@Override
public synchronized long transferTo(long position, long count,
WritableByteChannel target) throws IOException {
checkNotNull(target);
checkNotNegative(position, "position");
checkNotNegative(count, "count");
checkOpen();
checkReadable();
file.updateAccessTime();
return store.transferTo((int) position, (int) count, target);
}
@Override
public synchronized long transferFrom(ReadableByteChannel src,
long position, long count) throws IOException {
checkNotNull(src);
checkNotNegative(position, "position");
checkNotNegative(count, "count");
checkOpen();
checkWritable();
file.updateModifiedTime();
if (append) {
long appended = store.appendFrom(src, (int) count);
this.position = store.sizeInBytes();
return appended;
} else {
return store.transferFrom(src, (int) position, (int) count);
}
}
@Override
public synchronized int read(ByteBuffer dst, long position) throws IOException {
checkNotNull(dst);
checkNotNegative(position, "position");
checkOpen();
checkReadable();
file.updateAccessTime();
return store.read((int) position, dst);
}
@Override
public synchronized int write(ByteBuffer src, long position) throws IOException {
checkNotNull(src);
checkNotNegative(position, "position");
checkOpen();
checkWritable();
file.updateModifiedTime();
int written;
if (append) {
written = store.append(src);
this.position = store.sizeInBytes();
} else {
written = store.write((int) position, src);
}
return written;
}
@Override
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
// would like this to pretend to work, but can't create an implementation of MappedByteBuffer
throw new UnsupportedOperationException();
}
// TODO(cgdecker): Throw UOE from these lock methods since we aren't really supporting it?
@Override
public synchronized FileLock lock(long position, long size, boolean shared) throws IOException {
checkNotNegative(position, "position");
checkNotNegative(size, "size");
checkOpen();
if (shared) {
// shared is for a read lock
checkReadable();
} else {
// non-shared is for a write lock
checkWritable();
}
return new FakeFileLock(this, position, size, shared);
}
@Override
public FileLock tryLock(long position, long size, boolean shared) throws IOException {
// lock doesn't wait anyway
return lock(position, size, shared);
}
@Override
protected synchronized void implCloseChannel() throws IOException {
// if the file has been deleted, allow it to be GCed even if a reference to this channel is
// held after closing for some reason
file = null;
store = null;
}
/**
* A file lock that does nothing, since only one JVM process has access to this file system.
*/
static final class FakeFileLock extends FileLock {
private boolean valid = true;
public FakeFileLock(FileChannel channel, long position, long size, boolean shared) {
super(channel, position, size, shared);
}
public FakeFileLock(AsynchronousFileChannel channel, long position, long size, boolean shared) {
super(channel, position, size, shared);
}
@Override
public boolean isValid() {
return valid;
}
@Override
public void release() throws IOException {
valid = false;
}
}
static void checkNotNegative(long n, String type) {
checkArgument(n >= 0, "%s must not be negative: %s", type, n);
}
}