/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.tools.rpclib.multiplex;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;
/**
* An object that provides an {@link java.io.InputStream} interface to read data that has been written to a
* {@link java.io.OutputStream}.
* <p/>
* Buffers passed to {@link java.io.OutputStream#write} on the {@link #source} stream are <b>not</b> internally copied,
* and are assumed immutable. Mutation of any buffers passed to the {@link java.io.OutputStream#write} after the method
* has returned will result in undefined behaviour when calling the {@link #read} methods.
* <p/>
* Note: This is similar to {@link java.io.PipedInputStream} and {@link java.io.PipedOutputStream}, except this
* implementation does not use an internal ring buffer, and and does not suffer from 1 second stalls (JDK-4404700).
*/
public class PipeInputStream extends InputStream {
private static final Item ITEM_CLOSE = new Item(null, 0, 0);
private final OutputStream mSource;
private final LinkedList<Item> mQueue;
private final Semaphore mSemaphore;
private final byte[] mByte;
PipeInputStream() {
mQueue = new LinkedList<Item>();
mSemaphore = new Semaphore(0);
mByte = new byte[1];
mSource = new Writer();
}
public OutputStream getSource() {
return mSource;
}
@Override
public int read() throws IOException {
return (read(mByte, 0, 1) > 0) ? mByte[0] : -1;
}
@Override
public int read(byte b[], int off, int len) throws IOException {
int n = 0;
boolean closed = false;
while (!closed && len > n) {
try {
mSemaphore.acquire();
synchronized (mQueue) {
Item item = mQueue.getFirst();
if (item != ITEM_CLOSE) {
n += item.read(b, off + n, len - n);
if (item.remaining() == 0) {
mQueue.removeFirst();
}
else {
mSemaphore.release();
}
}
else {
closed = true;
}
}
}
catch (InterruptedException e) {
break;
}
}
if (n == 0 && closed) {
return -1;
}
return n;
}
private static class Item {
private final byte[] mData;
private final int mCount;
private int mOffset;
public Item(byte[] data, int offset, int count) {
mData = data;
mCount = count;
mOffset = offset;
}
public int read(byte[] out, int offset, int count) {
int remaining = remaining();
if (count > remaining) {
count = remaining;
}
System.arraycopy(mData, mOffset, out, offset, count);
mOffset += count;
return count;
}
public int remaining() {
return mCount - mOffset;
}
}
private class Writer extends OutputStream {
@Override
public void write(int b) throws IOException {
write(new byte[]{(byte)b}, 0, 1);
}
@Override
public void write(byte b[], int off, int len) throws IOException {
if (len > 0) {
synchronized (mQueue) {
mQueue.addLast(new Item(b, off, len));
}
mSemaphore.release();
}
}
@Override
public void close() throws IOException {
synchronized (mQueue) {
mQueue.addLast(ITEM_CLOSE);
mSemaphore.release();
}
}
}
}