blob: 880c4f1c913bd070d28d902dd8652dc7b8752c7d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/*
* Android Notice
* In this class the address length was changed from long to int.
* This is due to performance optimizations for the device.
*
* Also this class was copied from a newer version of harmony.
*/
package org.apache.harmony.nio.internal;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.MappedByteBufferAdapter;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import org.apache.harmony.luni.platform.IFileSystem;
import org.apache.harmony.luni.platform.Platform;
import org.apache.harmony.luni.platform.PlatformAddress;
import org.apache.harmony.luni.platform.PlatformAddressFactory;
/*
* The file channel impl class is the bridge between the logical channels
* described by the NIO channel framework, and the file system implementation
* provided by the port layer.
*
* This class is non-API, but implements the API of the FileChannel interface.
*/
public abstract class FileChannelImpl extends FileChannel {
// Reference to the portable file system code.
private static final IFileSystem fileSystem = Platform.getFileSystem();
private static final int ALLOC_GRANULARITY;
static {
try {
ALLOC_GRANULARITY = fileSystem.getAllocGranularity();
} catch (IOException e) {
throw new Error(e);
}
}
// Handle to the open file
private final int handle;
// The object that will track all outstanding locks on this channel.
private final LockManager lockManager = new LockManager();
private static class RepositioningLock {}
private final Object repositioningLock = new RepositioningLock();
private final Object stream;
/*
* Create a new file channel implementation class that wraps the given file
* handle and operates in the specified mode.
*
*/
public FileChannelImpl(Object stream, int handle) {
super();
this.handle = handle;
this.stream = stream;
}
/*
* Helper method to throw an exception if the channel is already closed.
* Note that we don't bother to synchronize on this test since the file may
* be closed by operations beyond our control anyways.
*/
protected final void openCheck() throws ClosedChannelException {
if (!isOpen()) {
throw new ClosedChannelException();
}
}
/*
* (non-Javadoc)
*
* @see java.nio.channels.spi.AbstractInterruptibleChannel#implCloseChannel()
*/
protected void implCloseChannel() throws IOException {
if (stream instanceof Closeable) {
((Closeable) stream).close();
}
}
protected FileLock basicLock(long position, long size, boolean shared,
boolean wait) throws IOException {
if (position < 0 || size < 0) {
throw new IllegalArgumentException("Lock position and size must be non-negative");
}
int lockType = shared ? IFileSystem.SHARED_LOCK_TYPE : IFileSystem.EXCLUSIVE_LOCK_TYPE;
FileLock pendingLock = new FileLockImpl(this, position, size, shared);
lockManager.addLock(pendingLock);
if (fileSystem.lock(handle, position, size, lockType, wait)) {
return pendingLock;
}
// Lock acquisition failed
lockManager.removeLock(pendingLock);
return null;
}
/*
* Acquire a lock on the receiver, blocks if the lock cannot be obtained
* immediately.
*
* @see java.nio.channels.FileChannel#lock(long, long, boolean)
*/
public final FileLock lock(long position, long size, boolean shared)
throws IOException {
openCheck();
FileLock resultLock = null;
{
boolean completed = false;
try {
begin();
resultLock = basicLock(position, size, shared, true);
completed = true;
} finally {
end(completed);
}
}
return resultLock;
}
/*
* Attempts to acquire the given lock, but does not block. If the lock
* cannot be acquired the method returns null.
*
* @see java.nio.channels.FileChannel#tryLock(long, long, boolean)
*/
public final FileLock tryLock(long position, long size, boolean shared)
throws IOException {
openCheck();
return basicLock(position, size, shared, false);
}
/*
* Non-API method to release a given lock on a file channel. Assumes that
* the lock will mark itself invalid after successful unlocking.
*/
void release(FileLock lock) throws IOException {
openCheck();
fileSystem.unlock(handle, lock.position(), lock.size());
lockManager.removeLock(lock);
}
@Override public void force(boolean metadata) throws IOException {
openCheck();
fileSystem.fsync(handle, metadata);
}
public abstract MappedByteBuffer map(MapMode mode, long position, long size) throws IOException;
protected final MappedByteBuffer mapImpl(MapMode mapMode, long position, long size)
throws IOException {
if (position + size > size()) {
fileSystem.truncate(handle, position + size);
}
long alignment = position - position % ALLOC_GRANULARITY;
int offset = (int) (position - alignment);
PlatformAddress address = PlatformAddressFactory.allocMap(handle,
alignment, size + offset, mapMode);
return new MappedByteBufferAdapter(address, (int) size, offset, mapMode);
}
/*
* Returns the current file position.
*/
public long position() throws IOException {
openCheck();
return fileSystem.seek(handle, 0L, IFileSystem.SEEK_CUR);
}
/*
* Sets the file pointer.
*/
public FileChannel position(long newPosition) throws IOException {
openCheck();
if (newPosition < 0) {
throw new IllegalArgumentException("New position must be non-negative");
}
synchronized (repositioningLock) {
fileSystem.seek(handle, newPosition, IFileSystem.SEEK_SET);
}
return this;
}
public int read(ByteBuffer buffer, long position) throws IOException {
FileChannelImpl.checkWritable(buffer);
if (position < 0) {
throw new IllegalArgumentException();
}
openCheck();
if (!buffer.hasRemaining()) {
return 0;
}
synchronized (repositioningLock) {
int bytesRead = 0;
long preReadPosition = position();
position(position);
try {
bytesRead = read(buffer);
} finally {
position(preReadPosition);
}
return bytesRead;
}
}
public int read(ByteBuffer buffer) throws IOException {
FileChannelImpl.checkWritable(buffer);
openCheck();
if (!buffer.hasRemaining()) {
return 0;
}
boolean completed = false;
int bytesRead = 0;
synchronized (repositioningLock) {
if (buffer.isDirect()) {
DirectBuffer directBuffer = (DirectBuffer) buffer;
int address = directBuffer.getEffectiveAddress().toInt();
try {
begin();
/*
* if (bytesRead <= EOF) dealt by read completed = false;
*/
bytesRead = (int) fileSystem.readDirect(handle, address,
buffer.position(), buffer.remaining());
completed = true;
} finally {
end(completed && bytesRead >= 0);
}
} else {
try {
begin();
/*
* if (bytesRead <= EOF) dealt by read completed = false;
*/
bytesRead = (int) fileSystem.read(handle, buffer.array(),
buffer.arrayOffset() + buffer.position(), buffer
.remaining());
completed = true;
} finally {
end(completed && bytesRead >= 0);
}
}
if (bytesRead > 0) {
buffer.position(buffer.position() + bytesRead);
}
}
return bytesRead;
}
public long read(ByteBuffer[] buffers, int offset, int length) throws IOException {
if (offset < 0 || length < 0 || offset + length > buffers.length) {
throw new IndexOutOfBoundsException();
}
openCheck();
int count = FileChannelImpl.calculateTotalRemaining(buffers, offset, length, true);
if (count == 0) {
return 0;
}
ByteBuffer[] directBuffers = new ByteBuffer[length];
int[] handles = new int[length];
int[] offsets = new int[length];
int[] lengths = new int[length];
for (int i = 0; i < length; i++) {
ByteBuffer buffer = buffers[i + offset];
if (!buffer.isDirect()) {
buffer = ByteBuffer.allocateDirect(buffer.remaining());
directBuffers[i] = buffer;
offsets[i] = 0;
} else {
offsets[i] = buffer.position();
}
handles[i] = ((DirectBuffer) buffer).getEffectiveAddress().toInt();
lengths[i] = buffer.remaining();
}
long bytesRead = 0;
{
boolean completed = false;
try {
begin();
synchronized (repositioningLock) {
bytesRead = fileSystem.readv(handle, handles, offsets,
lengths, length);
}
completed = true;
/*
* if (bytesRead < EOF) //dealt by readv? completed = false;
*/
} finally {
end(completed);
}
}
int end = offset + length;
long bytesRemaining = bytesRead;
for (int i = offset; i < end && bytesRemaining > 0; i++) {
if (buffers[i].isDirect()) {
if (lengths[i] < bytesRemaining) {
int pos = buffers[i].limit();
buffers[i].position(pos);
bytesRemaining -= lengths[i];
} else {
int pos = (int) bytesRemaining;
buffers[i].position(pos);
break;
}
} else {
ByteBuffer buf = directBuffers[i - offset];
if (bytesRemaining < buf.remaining()) {
// this is the last step.
int pos = buf.position();
buffers[i].put(buf);
buffers[i].position(pos + (int) bytesRemaining);
bytesRemaining = 0;
} else {
bytesRemaining -= buf.remaining();
buffers[i].put(buf);
}
}
}
return bytesRead;
}
/*
* Returns the current file size, as an integer number of bytes.
*/
public long size() throws IOException {
openCheck();
return fileSystem.length(handle);
}
public long transferFrom(ReadableByteChannel src, long position, long count)
throws IOException {
openCheck();
if (!src.isOpen()) {
throw new ClosedChannelException();
}
if (position < 0 || count < 0 || count > Integer.MAX_VALUE) {
throw new IllegalArgumentException();
}
if (position > size()) {
return 0;
}
ByteBuffer buffer = null;
try {
if (src instanceof FileChannel) {
FileChannel fileSrc = (FileChannel) src;
long size = fileSrc.size();
long filePosition = fileSrc.position();
count = Math.min(count, size - filePosition);
buffer = fileSrc.map(MapMode.READ_ONLY, filePosition, count);
fileSrc.position(filePosition + count);
} else {
buffer = ByteBuffer.allocateDirect((int) count);
src.read(buffer);
buffer.flip();
}
return write(buffer, position);
} finally {
// unmap the buffer
if (buffer != null) {
// all children of FileChannelImpl currently returns
// an instance of DirectBuffer from map() method
((DirectBuffer) buffer).free();
}
}
}
public long transferTo(long position, long count, WritableByteChannel target)
throws IOException {
openCheck();
if (!target.isOpen()) {
throw new ClosedChannelException();
}
if (target instanceof ReadOnlyFileChannel) {
throw new NonWritableChannelException();
}
if (position < 0 || count < 0) {
throw new IllegalArgumentException();
}
if (count == 0 || position >= size()) {
return 0;
}
ByteBuffer buffer = null;
count = Math.min(count, size() - position);
if (target instanceof SocketChannelImpl) {
// only socket can be transfered by system call
return kernelTransfer(handle, ((SocketChannelImpl) target).getFD(),
position, count);
}
try {
buffer = map(MapMode.READ_ONLY, position, count);
return target.write(buffer);
} finally {
// unmap the buffer
if (buffer != null) {
// all children of FileChannelImpl currently returns
// an instance of DirectBuffer from map() method
((DirectBuffer) buffer).free();
}
}
}
private long kernelTransfer(int l, FileDescriptor fd, long position,
long count) throws IOException {
boolean completed = false;
try {
begin();
long ret = fileSystem.transfer(l, fd, position, count);
completed = true;
return ret;
} finally {
end(completed);
}
}
public FileChannel truncate(long size) throws IOException {
openCheck();
if (size < 0) {
throw new IllegalArgumentException();
}
if (size < size()) {
synchronized (repositioningLock) {
long position = position();
fileSystem.truncate(handle, size);
/*
* FIXME: currently the port library always modifies the
* position to given size. not sure it is a bug or intended
* behaviour, so I always reset the position to proper value as
* Java Spec.
*/
position(position > size ? size : position);
}
}
return this;
}
/*
* (non-Javadoc)
*
* @see java.nio.channels.WritableByteChannel#write(java.nio.ByteBuffer)
*/
public int write(ByteBuffer buffer, long position) throws IOException {
if (null == buffer) {
throw new NullPointerException();
}
if (position < 0) {
throw new IllegalArgumentException();
}
openCheck();
if (!buffer.hasRemaining()) {
return 0;
}
int bytesWritten = 0;
synchronized (repositioningLock) {
long preWritePosition = position();
position(position);
try {
bytesWritten = writeImpl(buffer);
} finally {
position(preWritePosition);
}
}
return bytesWritten;
}
public int write(ByteBuffer buffer) throws IOException {
openCheck();
return writeImpl(buffer);
}
private int writeImpl(ByteBuffer buffer) throws IOException {
int bytesWritten;
boolean completed = false;
synchronized (repositioningLock) {
if (buffer.isDirect()) {
DirectBuffer directBuffer = (DirectBuffer) buffer;
int address = directBuffer.getEffectiveAddress().toInt();
try {
begin();
bytesWritten = (int) fileSystem.writeDirect(handle,
address, buffer.position(), buffer.remaining());
completed = true;
} finally {
end(completed);
}
} else {
try {
begin();
bytesWritten = (int) fileSystem.write(handle, buffer
.array(), buffer.arrayOffset() + buffer.position(),
buffer.remaining());
completed = true;
} finally {
end(completed);
}
}
if (bytesWritten > 0) {
buffer.position(buffer.position() + bytesWritten);
}
}
return bytesWritten;
}
public long write(ByteBuffer[] buffers, int offset, int length) throws IOException {
if (offset < 0 || length < 0 || (offset + length) > buffers.length) {
throw new IndexOutOfBoundsException();
}
openCheck();
int count = FileChannelImpl.calculateTotalRemaining(buffers, offset, length, false);
if (count == 0) {
return 0;
}
int[] handles = new int[length];
int[] offsets = new int[length];
int[] lengths = new int[length];
// BEGIN android-changed
// list of allocated direct ByteBuffers to prevent them from being GC-ed
DirectBuffer[] allocatedBufs = new DirectBuffer[length];
for (int i = 0; i < length; i++) {
ByteBuffer buffer = buffers[i + offset];
if (!buffer.isDirect()) {
ByteBuffer directBuffer = ByteBuffer.allocateDirect(buffer.remaining());
directBuffer.put(buffer);
directBuffer.flip();
buffer = directBuffer;
allocatedBufs[i] = (DirectBuffer) directBuffer;
offsets[i] = 0;
} else {
offsets[i] = buffer.position();
allocatedBufs[i] = null;
}
handles[i] = ((DirectBuffer) buffer).getEffectiveAddress().toInt();
lengths[i] = buffer.remaining();
}
// END android-changed
long bytesWritten = 0;
boolean completed = false;
synchronized (repositioningLock) {
try {
begin();
bytesWritten = fileSystem.writev(handle, handles, offsets,
lengths, length);
completed = true;
} finally {
end(completed);
// BEGIN android-added
// free temporary direct buffers
for (int i = 0; i < length; ++i) {
if (allocatedBufs[i] != null) {
allocatedBufs[i].free();
}
}
// END android-added
}
}
long bytesRemaining = bytesWritten;
for (int i = offset; i < length + offset; i++) {
if (bytesRemaining > buffers[i].remaining()) {
int pos = buffers[i].limit();
buffers[i].position(pos);
bytesRemaining -= buffers[i].remaining();
} else {
int pos = buffers[i].position() + (int) bytesRemaining;
buffers[i].position(pos);
break;
}
}
return bytesWritten;
}
static void checkWritable(ByteBuffer buffer) {
if (buffer.isReadOnly()) {
throw new IllegalArgumentException("read-only buffer");
}
}
/**
* @param copyingIn true if we're copying data into the buffers (typically
* because the caller is a file/network read operation), false if we're
* copying data out of the buffers (for a file/network write operation).
*/
static int calculateTotalRemaining(ByteBuffer[] buffers, int offset, int length, boolean copyingIn) {
int count = 0;
for (int i = offset; i < offset + length; ++i) {
count += buffers[i].remaining();
if (copyingIn) {
checkWritable(buffers[i]);
}
}
return count;
}
public int getHandle() {
return handle;
}
}