/*******************************************************************************
* Copyright (c) 2013 Xilinx, 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:
* Xilinx - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.services.IStreams;
/**
* TCFVirtualInputStream is InputStream implementation over TCF Streams service.
*
* @since 1.2
*/
public final class TCFVirtualInputStream extends InputStream {
private static final int MAX_QUEUE = 8;
private static class Buffer {
IToken token;
Exception error;
byte[] buf;
int pos;
boolean eof;
}
private final IChannel channel;
private final IStreams streams;
private final String id;
private final Runnable on_close;
private final LinkedList<Buffer> queue = new LinkedList<Buffer>();
private Buffer buf;
private TCFTask<Buffer> task;
private byte[] tmp = new byte[1];
private boolean closed;
private boolean eof;
public TCFVirtualInputStream(IChannel channel, String id, Runnable on_close) throws IOException {
this.channel = channel;
streams = channel.getRemoteService(IStreams.class);
if (streams == null) throw new IOException("Streams service not available"); //$NON-NLS-1$
this.id = id;
this.on_close = on_close;
}
@Override
public synchronized int read(byte b[], final int off, final int len) throws IOException {
if (closed) throw new IOException("Stream is closed"); //$NON-NLS-1$
if (b == null) throw new NullPointerException();
if (off < 0 || off > b.length || len < 0 || len > b.length - off) throw new IndexOutOfBoundsException();
if (len == 0) return 0;
try {
for (;;) {
if (buf == null) {
buf = new TCFTask<Buffer>() {
public void run() {
while (!eof && queue.size() < MAX_QUEUE) {
final Buffer nxt = new Buffer();
queue.add(nxt);
nxt.token = streams.read(id, 0x10000, new IStreams.DoneRead() {
public void doneRead(IToken token, Exception error, int lost, byte[] data, boolean eos) {
assert nxt.token == token;
nxt.token = null;
nxt.error = error;
nxt.buf = data;
nxt.eof = eos;
if (!eof && (eos || error != null)) eof = true;
if (task != null) {
assert queue.getFirst() == nxt;
task.done(queue.removeFirst());
task = null;
}
}
});
}
if (queue.getFirst().token == null) {
done(queue.removeFirst());
}
else {
task = this;
}
}
}.getIO();
}
if (buf.buf != null && buf.pos < buf.buf.length) {
int n = len;
if (n > buf.buf.length - buf.pos) n = buf.buf.length - buf.pos;
System.arraycopy(buf.buf, buf.pos, b, off, n);
buf.pos += n;
return n;
}
if (buf.error instanceof IOException) throw (IOException)buf.error;
if (buf.error != null) throw new IOException(buf.error);
if (buf.eof) return -1;
buf = null;
}
}
catch (IOException e) {
if (closed) return -1;
throw e;
}
}
@Override
public synchronized int read() throws IOException {
if (!closed && buf != null && buf.buf != null && buf.pos < buf.buf.length) {
return buf.buf[buf.pos++] & 0xff;
}
int n = read(tmp, 0, 1);
if (n < 0) return -1;
assert n == 1;
return tmp[0] & 0xff;
}
@Override
public void close() throws IOException {
if (closed) return;
closed = true;
new TCFTask<Object>() {
public void run() {
streams.disconnect(id, new IStreams.DoneDisconnect() {
public void doneDisconnect(IToken token, Exception error) {
if (error != null && channel.getState() != IChannel.STATE_CLOSED) {
error(error);
}
else {
if (on_close != null) on_close.run();
done(this);
}
}
});
}
}.getIO();
}
}