package com.robonobo.common.io;
/*
* Robonobo Common Utils
* Copyright (C) 2008 Will Morton (macavity@well.com) & Ray Hilton (ray@wirestorm.net)
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import com.robonobo.common.util.TextUtil;
/**
* This ByteBuffer acts as a pipe, a source OutputStream delivers bytes to one end, and a
* sink InputStream reads them from the other end. The buffer dynamically grows and shrinks
* as is needed.
*
* There is a maxBufferSize, if the buffer attempts to go above this, an IOException is thrown.
*
* This source code file is Copyright 2003-2008 Ray Hilton / Will Morton. All
* rights reserved. Unauthorised duplication of this file is expressly forbidden
* without prior written permission.
*/
public class PipeByteBuffer {
private static final int DEFAULT_SIZE = 16;
private byte[] buf;
private int firstIndex;
private int bytesInBuffer;
private boolean closed;
private int maxBufferSize = 2 * 1024 * 1024; // 2Mb
public PipeByteBuffer() {
this(new byte[DEFAULT_SIZE]);
bytesInBuffer = 0;
}
public PipeByteBuffer(byte[] arr) {
bytesInBuffer = arr.length;
buf = new byte[bytesInBuffer];
System.arraycopy(arr, 0, buf, 0, bytesInBuffer);
firstIndex = 0;
}
public PipeByteBuffer(int maxBufferSize) {
this();
this.maxBufferSize = maxBufferSize;
bytesInBuffer = 0;
}
public synchronized long getLength() {
return bytesInBuffer;
}
public synchronized long getCurrentBufferSize() {
return buf.length;
}
public boolean isClosed() {
return closed;
}
public PipeInputStream getSink() {
return new PipeInputStream();
}
public PipeOutputStream getSource() {
return new PipeOutputStream();
}
public class PipeInputStream extends InputStream {
public int read() throws IOException {
synchronized(PipeByteBuffer.this) {
// Block if no data (unless we're closed)
while(bytesInBuffer < 1) {
if(closed) {
return -1;
}
try {
PipeByteBuffer.this.wait();
} catch(InterruptedException e) {
throw new IOException("Blocking read was interrupted");
}
}
byte b = buf[firstIndex];
firstIndex = (firstIndex + 1) % buf.length;
bytesInBuffer--;
// check to see if we need to shrink
// Disabled shrinking for the mo - leave it at max size
//shrinkBuffer();
PipeByteBuffer.this.notifyAll();
return b;
}
}
public int read(byte[] readArr, int off, int len) throws IOException {
synchronized(PipeByteBuffer.this) {
// Block if no data (unless we're closed)
while(bytesInBuffer < 1) {
if(closed) {
return -1;
}
try {
PipeByteBuffer.this.wait();
} catch(InterruptedException e) {
throw new IOException("Blocking read was interrupted");
}
}
int numToRead = len;
if(numToRead > bytesInBuffer)
numToRead = bytesInBuffer;
for(int i=0;i<numToRead;i++) {
readArr[off+i] = buf[firstIndex];
firstIndex = (firstIndex + 1) % buf.length;
bytesInBuffer--;
}
PipeByteBuffer.this.notifyAll();
return numToRead;
}
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public void close() throws IOException {
super.close();
PipeByteBuffer.this.close();
}
}
public class PipeOutputStream extends OutputStream {
public void write(int arg0) throws IOException {
synchronized(PipeByteBuffer.this) {
if(closed)
throw new IOException("Pipe is closed");
// If our buffer is getting large, wait until someone reads some data
while(!canBuffer(1)) {
if(closed)
return;
try {
PipeByteBuffer.this.wait();
} catch(InterruptedException e) {
throw new IOException("Blocking write was interrupted");
}
}
while(1 > (buf.length - bytesInBuffer)) {
expandBuffer();
}
int thisIndex = (firstIndex + bytesInBuffer) % buf.length;
buf[thisIndex] = (byte) (arg0 & 0xff);
bytesInBuffer++;
PipeByteBuffer.this.notifyAll();
}
}
public void write(byte[] writeArr, int off, int len) throws IOException {
synchronized(PipeByteBuffer.this) {
if(closed)
throw new IOException("Pipe is closed");
// If our buffer is getting large, wait until someone reads some data
while(!canBuffer(len)) {
if(closed)
return;
try {
PipeByteBuffer.this.wait();
} catch(InterruptedException e) {
throw new IOException("Blocking write was interrupted");
}
}
while(len > (buf.length - bytesInBuffer)) {
expandBuffer();
}
for(int i=0;i<len;i++) {
int thisIndex = (firstIndex + bytesInBuffer) % buf.length;
buf[thisIndex] = writeArr[off+i];
bytesInBuffer++;
}
PipeByteBuffer.this.notifyAll();
}
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void close() throws IOException {
super.close();
PipeByteBuffer.this.close();
}
}
/**
* Indicates that no more writing will be done to the PipeStream,
* and that future read()s
* should return 0 when the stream is empty
*/
public synchronized void close() {
closed = true;
notifyAll();
}
/**
* clears the buffer and resets the buffer size.
*
*/
public synchronized void clear() {
bytesInBuffer = 0;
firstIndex = 0;
buf = new byte[DEFAULT_SIZE];
}
private boolean canBuffer(int numBytes) {
return ((bytesInBuffer + numBytes) <= maxBufferSize);
}
private void expandBuffer() throws IOException {
// We just double the size and copy the contents to the new
// buffer
if(buf.length == 0) {
buf = new byte[DEFAULT_SIZE];
firstIndex = 0;
return;
}
byte[] newBuf = new byte[buf.length * 2];
for(int i = 0; i < bytesInBuffer; i++) {
int thisIndex = (firstIndex + i) % buf.length;
newBuf[i] = buf[thisIndex];
}
firstIndex = 0;
buf = newBuf;
}
public int getMaxBufferSize() {
return maxBufferSize;
}
public void setMaxBufferSize(int maxBufferSize) {
this.maxBufferSize = maxBufferSize;
}
public synchronized void printBufferState(PrintStream out) {
out.println("firstIndex="+firstIndex+", length="+bytesInBuffer);
for(int i=0;i<buf.length;i++) {
out.print(TextUtil.rightPad(Integer.toString(i), 4));
}
out.println();
for(int i=0;i<buf.length;i++) {
out.print(TextUtil.rightPad(Byte.toString(buf[i]), 4));
}
out.println();
}
}