/*******************************************************************************
* Copyright (c) 2007, 2013 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedList;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IFileSystem;
import org.eclipse.tcf.services.IFileSystem.FileSystemException;
import org.eclipse.tcf.services.IFileSystem.IFileHandle;
/**
* TCFFileInputStream is high performance InputStream implementation over TCF FileSystem service.
* The class uses read-ahead buffers to achieve maximum throughput.
*/
public final class TCFFileInputStream extends InputStream {
private static final int MAX_READ_AHEAD = 8;
private static class Buffer {
final long offset;
IToken token;
byte[] buf;
boolean eof;
Buffer(long offset) {
this.offset = offset;
}
@Override
public String toString() {
return "[" + offset + ":" + (buf == null ? "null" : Integer.toString(buf.length)) + "]";
}
}
private final IFileHandle handle;
private final IFileSystem fs;
private final int buf_size;
private long mark = 0;
private long offset = 0;
private Buffer buf;
private boolean closed = false;
private boolean suspend_read_ahead;
private Runnable waiting_client;
private final LinkedList<Buffer> read_ahead_buffers = new LinkedList<Buffer>();
public TCFFileInputStream(IFileHandle handle) {
this(handle, 0x1000);
}
public TCFFileInputStream(IFileHandle handle, int buf_size) {
this.handle = handle;
this.fs = handle.getService();
this.buf_size = buf_size;
}
private void startReadAhead(Buffer prv) {
if (suspend_read_ahead) return;
if (read_ahead_buffers.size() > 0) {
prv = read_ahead_buffers.getLast();
}
if (prv.eof) return;
long pos = prv.offset + (prv.buf == null ? buf_size : prv.buf.length);
while (read_ahead_buffers.size() < MAX_READ_AHEAD) {
final Buffer buf = new Buffer(pos);
buf.token = fs.read(handle, pos, buf_size, new IFileSystem.DoneRead() {
public void doneRead(IToken token, FileSystemException error,
byte[] data, boolean eof) {
assert buf.token == token;
assert read_ahead_buffers.contains(buf);
buf.token = null;
if (error != null) {
suspend_read_ahead = true;
read_ahead_buffers.remove(buf);
}
else if (data.length != buf_size) {
buf.buf = data;
buf.eof = eof;
if (!eof) suspend_read_ahead = true;
}
else {
buf.buf = data;
buf.eof = eof;
startReadAhead(buf);
}
if (waiting_client != null) {
Protocol.invokeLater(waiting_client);
waiting_client = null;
}
}
});
read_ahead_buffers.add(buf);
pos += buf_size;
}
}
private boolean stopReadAhead(Runnable done) {
suspend_read_ahead = true;
for (Iterator<Buffer> i = read_ahead_buffers.iterator(); i.hasNext();) {
Buffer buf = i.next();
if (buf.token == null || buf.token.cancel()) i.remove();
}
if (read_ahead_buffers.size() > 0) {
assert waiting_client == null;
waiting_client = done;
return false;
}
return true;
}
@Override
public synchronized int read() throws IOException {
if (closed) throw new IOException("Stream is closed");
while (buf == null || buf.offset > offset || buf.offset + buf.buf.length <= offset) {
if (buf != null && buf.eof) return -1;
buf = new TCFTask<Buffer>() {
public void run() {
assert waiting_client == null;
while (read_ahead_buffers.size() > 0) {
Buffer buf = read_ahead_buffers.getFirst();
if (buf.offset == offset) {
if (buf.token != null) {
waiting_client = this;
}
else {
startReadAhead(buf);
read_ahead_buffers.remove(buf);
done(buf);
}
return;
}
suspend_read_ahead = true;
if (buf.token != null && buf.token.cancel()) buf.token = null;
if (buf.token != null) {
waiting_client = this;
return;
}
read_ahead_buffers.remove(buf);
}
fs.read(handle, offset, buf_size, new IFileSystem.DoneRead() {
public void doneRead(IToken token, FileSystemException error,
byte[] data, boolean eof) {
if (error != null) {
error(error);
return;
}
assert data != null && data.length <= buf_size;
Buffer buf = new Buffer(offset);
buf.buf = data;
buf.eof = eof;
if (!eof) {
suspend_read_ahead = false;
startReadAhead(buf);
}
done(buf);
}
});
}
}.getIO();
assert buf.token == null;
}
int ofs = (int)(offset++ - buf.offset);
return buf.buf[ofs] & 0xff;
}
@Override
public synchronized int read(final byte arr[], final int off, final int len) throws IOException {
if (closed) throw new IOException("Stream is closed");
if (arr == null) throw new NullPointerException();
if (off < 0 || len < 0 || len > arr.length - off) throw new IndexOutOfBoundsException();
int pos = 0;
while (pos < len) {
if (buf != null && buf.offset <= offset && buf.offset + buf.buf.length > offset) {
int buf_pos = (int)(offset - buf.offset);
int buf_len = buf.buf.length - buf_pos;
int n = len - pos < buf_len ? len - pos : buf_len;
System.arraycopy(buf.buf, buf_pos, arr, off + pos, n);
pos += n;
offset += n;
}
else {
int c = read();
if (c == -1) {
if (pos == 0) return -1;
break;
}
arr[off + pos++] = (byte)c;
}
}
return pos;
}
@Override
public boolean markSupported() {
return true;
}
@Override
public synchronized void reset() throws IOException {
if (closed) throw new IOException("Stream is closed");
offset = mark;
if (buf != null && buf.offset <= offset && buf.offset + buf.buf.length > offset) return;
new TCFTask<Object>() {
public void run() {
if (!stopReadAhead(this)) return;
done(this);
}
}.getIO();
buf = null;
}
@Override
public synchronized void mark(int readlimit) {
mark = offset;
}
@Override
public synchronized void close() throws IOException {
if (closed) return;
new TCFTask<Object>() {
public void run() {
if (!stopReadAhead(this)) return;
assert read_ahead_buffers.isEmpty();
fs.close(handle, new IFileSystem.DoneClose() {
public void doneClose(IToken token, FileSystemException error) {
if (error != null) error(error);
else done(this);
}
});
}
}.getIO();
closed = true;
buf = null;
}
}