/*
* Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 David Berkman
*
* This file is part of the SmallMind Code Project.
*
* The SmallMind Code Project is free software, you can redistribute
* it and/or modify it under either, at your discretion...
*
* 1) The terms of GNU Affero General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* ...or...
*
* 2) The terms of the Apache License, Version 2.0.
*
* The SmallMind Code Project 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 or Apache License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and the Apache License along with the SmallMind Code Project. If not, see
* <http://www.gnu.org/licenses/> or <http://www.apache.org/licenses/LICENSE-2.0>.
*
* Additional permission under the GNU Affero GPL version 3 section 7
* ------------------------------------------------------------------
* If you modify this Program, or any covered work, by linking or
* combining it with other code, such other code is not for that reason
* alone subject to any of the requirements of the GNU Affero GPL
* version 3.
*/
package org.smallmind.nutsnbolts.io;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
public class ByteArrayIOStream implements Closeable {
private final ByteArrayInputStream inputStream = new ByteArrayInputStream();
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final LinkedList<byte[]> segmentList = new LinkedList<>();
private final int allocation;
private boolean closed = false;
private int readIndex = 0;
private int writeIndex;
public ByteArrayIOStream () {
this(1024);
}
public ByteArrayIOStream (int allocation) {
this.allocation = allocation;
writeIndex = allocation;
}
@Override
public void close () {
synchronized (segmentList) {
closed = true;
segmentList.clear();
}
}
public ByteArrayInputStream asInputStream () {
return inputStream;
}
public ByteArrayOutputStream asOutputStream () {
return outputStream;
}
public class ByteArrayInputStream extends InputStream {
private LinkedList<byte[]> markList;
private int readLimit = 0;
private int markIndex = 0;
public byte peek (int index)
throws IOException {
synchronized (segmentList) {
if (closed) {
throw new IOException("This stream has already been closed");
} else if (index < 0 || index >= available()) {
throw new IndexOutOfBoundsException(index + ">=" + available());
} else {
int bytesToSeek = index;
if (bytesToSeek < allocation - readIndex) {
return segmentList.getFirst()[readIndex + bytesToSeek];
} else {
int segmentIndex = 1;
bytesToSeek -= allocation - readIndex;
while (bytesToSeek >= allocation) {
bytesToSeek = allocation;
segmentIndex++;
}
return segmentList.get(segmentIndex)[bytesToSeek];
}
}
}
}
public byte[] readAvailable ()
throws IOException {
synchronized (segmentList) {
byte[] buffer = new byte[available()];
read(buffer);
return buffer;
}
}
@Override
public int read ()
throws IOException {
synchronized (segmentList) {
if (closed) {
throw new IOException("This stream has already been closed");
} else {
byte singleByte;
try {
while (available() == 0) {
segmentList.wait();
}
} catch (InterruptedException interruptedException) {
throw new IOException(interruptedException);
}
singleByte = segmentList.getFirst()[readIndex];
peelSegment(1);
return singleByte;
}
}
}
@Override
public int read (byte[] b, int off, int len)
throws IOException {
synchronized (segmentList) {
if (closed) {
throw new IOException("This stream has already been closed");
} else if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || off > b.length || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
} else {
int bytesAvailable;
try {
while ((bytesAvailable = available()) == 0) {
segmentList.wait();
}
} catch (InterruptedException interruptedException) {
throw new IOException(interruptedException);
}
int bytesToRead = Math.min(bytesAvailable, len);
int bytesRead = 0;
while (bytesRead < bytesToRead) {
int bytesToReadInSegment = Math.min(allocation - readIndex, bytesToRead - bytesRead);
System.arraycopy(segmentList.getFirst(), readIndex, b, off + bytesRead, bytesToReadInSegment);
bytesRead += bytesToReadInSegment;
peelSegment(bytesToReadInSegment);
}
return bytesRead;
}
}
}
@Override
public long skip (long n)
throws IOException {
long bytesToSkip;
if ((bytesToSkip = Math.min(available(), n)) > 0) {
long bytesSkipped = 0;
while (bytesSkipped < bytesToSkip) {
int bytesToSkipInSegment = (int)Math.min(allocation - readIndex, bytesToSkip - bytesSkipped);
bytesSkipped += bytesToSkipInSegment;
peelSegment(bytesToSkipInSegment);
}
return bytesSkipped;
}
return 0;
}
private void peelSegment (int readIncrement) {
if ((readIndex += readIncrement) == allocation) {
byte[] usedSegment = segmentList.removeFirst();
readIndex = 0;
if (markList != null) {
markList.add(usedSegment);
if (remembered() > readLimit) {
markList = null;
}
}
}
}
@Override
public int available ()
throws IOException {
synchronized (segmentList) {
if (closed) {
throw new IOException("This stream has already been closed");
}
return segmentList.isEmpty() ? 0 : (allocation * (segmentList.size() - 1)) + writeIndex - readIndex;
}
}
private int remembered () {
return (markList == null) ? 0 : (allocation * markList.size()) + readIndex - markIndex;
}
@Override
public void mark (int readLimit) {
synchronized (segmentList) {
if (!closed) {
this.readLimit = readLimit;
markList = new LinkedList<>();
markIndex = readIndex;
}
}
}
@Override
public void reset ()
throws IOException {
synchronized (segmentList) {
if (closed) {
throw new IOException("This stream has already been closed");
}
segmentList.addAll(0, markList);
readIndex = markIndex;
markList = null;
}
}
@Override
public void close () {
synchronized (segmentList) {
markList = null;
ByteArrayIOStream.this.close();
}
}
@Override
public boolean markSupported () {
return true;
}
}
public class ByteArrayOutputStream extends OutputStream {
@Override
public void write (int b)
throws IOException {
synchronized (segmentList) {
if (closed) {
throw new IOException("This stream has already been closed");
}
if (writeIndex == allocation) {
segmentList.add(new byte[allocation]);
writeIndex = 0;
}
segmentList.getLast()[writeIndex++] = (byte)b;
segmentList.notify();
}
}
@Override
public void write (byte[] b, int off, int len)
throws IOException {
synchronized (segmentList) {
if (closed) {
throw new IOException("This stream has already been closed");
} else if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || off > b.length || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len > 0) {
int bytesWritten = 0;
while (bytesWritten < len) {
int bytesToWriteInSegment = Math.min(allocation - writeIndex, len - bytesWritten);
if (bytesToWriteInSegment > 0) {
System.arraycopy(b, off + bytesWritten, segmentList.getLast(), writeIndex, bytesToWriteInSegment);
bytesWritten += bytesToWriteInSegment;
}
if ((writeIndex += bytesToWriteInSegment) == allocation) {
segmentList.add(new byte[allocation]);
writeIndex = 0;
}
}
}
}
}
@Override
public void close () {
ByteArrayIOStream.this.close();
}
}
}