/*
* Copyright 2006-2010 Brian S O'Neill
*
* 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 org.cojen.dirmi.io;
import java.io.InputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.cojen.dirmi.ClosedException;
import org.cojen.dirmi.RejectedException;
/**
* Unbuffered replacement for {@link java.io.PipedInputStream}. This piped
* stream does not have the flaws found in the java.io implementation. It
* allows multiple threads to read from it without interfering with the
* stream's state. Also it can't get into a one-second polling mode.
*
* @author Brian S O'Neill
* @see PipedOutputStream
*/
public class PipedInputStream extends InputStream {
private static final int NOT_CONNECTED = 0, CONNECTED = 1, HALF_CLOSED = 2, CLOSED = 3;
private final Lock mLock;
private PipedOutputStream mPout;
private int mConnectState;
private Queue<Channel.Listener> mListenerQueue;
public PipedInputStream() {
mLock = new ReentrantLock();
}
public PipedInputStream(PipedOutputStream pout) throws IOException {
mLock = pout.setInput(this);
setOutput(pout);
}
public int read() throws IOException {
mLock.lock();
try {
return mPout.read();
} catch (Exception e) {
checkHalfClosed(e);
return -1;
} finally {
mLock.unlock();
}
}
public int read(byte[] bytes) throws IOException {
return read(bytes, 0, bytes.length);
}
public int read(byte[] bytes, int offset, int length) throws IOException {
mLock.lock();
try {
return mPout.read(bytes, offset, length);
} catch (Exception e) {
checkHalfClosed(e);
return -1;
} finally {
mLock.unlock();
}
}
public long skip(long n) throws IOException {
mLock.lock();
try {
return mPout.skip(n);
} catch (Exception e) {
checkHalfClosed(e);
return 0;
} finally {
mLock.unlock();
}
}
public int available() throws IOException {
mLock.lock();
try {
return mPout.inputAvailable();
} catch (Exception e) {
checkHalfClosed(e);
return 0;
} finally {
mLock.unlock();
}
}
public boolean isReady() throws IOException {
return available() > 0;
}
public boolean isClosed() {
mLock.lock();
try {
return mConnectState == CLOSED;
} finally {
mLock.unlock();
}
}
public void close() {
mLock.lock();
try {
if (mPout != null) {
PipedOutputStream pout = mPout;
mPout = null;
pout.close();
}
mConnectState = CLOSED;
} finally {
mLock.unlock();
}
}
@Override
public String toString() {
String superStr = superToString();
mLock.lock();
try {
if (mPout == null) {
return superStr.concat(" (unconnected)");
} else {
return superStr + " connected to " + mPout.superToString();
}
} finally {
mLock.unlock();
}
}
void inputNotify(final IOExecutor executor, final Channel.Listener listener) {
mLock.lock();
try {
try {
int avail;
if (isReady()) {
new PipeNotify(executor, listener);
return;
}
} catch (IOException e) {
new PipeNotify(executor, listener, e);
return;
}
Queue<Channel.Listener> queue = mListenerQueue;
if (queue == null) {
mListenerQueue = queue = new LinkedList<Channel.Listener>();
}
queue.add(new Channel.Listener() {
public void ready() {
new PipeNotify(executor, listener);
}
public void rejected(RejectedException cause) {
listener.rejected(cause);
}
public void closed(IOException cause) {
new PipeNotify(executor, listener, cause);
}
});
} finally {
mLock.unlock();
}
}
// Caller must hold mLock.
void notifyReady() {
Queue<Channel.Listener> queue = mListenerQueue;
if (queue != null) {
Channel.Listener listener = queue.poll();
if (listener != null) {
listener.ready();
}
}
}
// Caller must hold mLock.
void notifyClosed() {
Queue<Channel.Listener> queue = mListenerQueue;
if (queue != null) {
ClosedException ex = new ClosedException();
Channel.Listener listener;
while ((listener = queue.poll()) != null) {
listener.closed(ex);
}
}
}
void outputClosed() {
mLock.lock();
try {
mPout = null;
if (mConnectState != CLOSED) {
mConnectState = HALF_CLOSED;
}
} finally {
mLock.unlock();
}
}
String superToString() {
return super.toString();
}
Lock setOutput(PipedOutputStream pout) throws IOException {
mLock.lock();
try {
switch (mConnectState) {
case NOT_CONNECTED:
mPout = pout;
mConnectState = CONNECTED;
return mLock;
case CONNECTED:
throw new IOException("Already connected");
default:
throw new ClosedException();
}
} finally {
mLock.unlock();
}
}
// Caller must hold mLock.
private void checkHalfClosed(Exception e) throws IOException {
if (mPout == null) {
if (mConnectState == HALF_CLOSED) {
return;
}
if (e instanceof NullPointerException) {
if (mConnectState == NOT_CONNECTED) {
e = new IOException("Not connected");
} else {
e = new ClosedException();
}
}
}
if (e instanceof IOException) {
throw (IOException) e;
}
throw new IOException(e);
}
}