hal/src/zutil/io/BufferedBoundaryInputStream.java

329 lines
10 KiB
Java
Executable file

/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Ziver Koc
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package zutil.io;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* A stream that is handled as a iterator. The stream will read
* until it gets to a boundary and will return -1 (end of stream).
* The stream will not return any data until the {@link #next()}
* method is called.
*
* @author Ziver
*
*/
public class BufferedBoundaryInputStream extends FilterInputStream{
/** The size of the buffer in bytes */
protected static final int DEFAULT_BUF_SIZE = 8192;
/** The raw buffer */
private byte buffer[];
/** The current position in the buffer */
private int buf_pos = 0;
/** The end position of the buffer */
private int buf_end = 0;
/** Boundary position, 0< means no boundary found */
private int buf_bound_pos = -1;
/** The boundary (the delimiter) */
private byte[] boundary;
/** The position in the buffer where user has marked, -1 if no mark is set **/
private int buf_mark = -1;
/** The read limit of the mark, after this limit the mark will be invalidated **/
private int buf_mark_limit = 0;
/**
* Creates a instance of this class with a default buffer size of 64K
*
* @param in is the InputStream that the buffer will use
*/
public BufferedBoundaryInputStream(InputStream in){
this(in, DEFAULT_BUF_SIZE);
}
/**
* Creates a instance of this class
*
* @param in is the InputStream that the buffer will use
* @param buf_size speifies the buffer size
*/
public BufferedBoundaryInputStream(InputStream in, int buf_size){
super(in);
buffer = new byte[buf_size];
}
/**
* @return the next byte from the stream or -1 if EOF or stream has encountered a boundary
*/
public int read() throws IOException{
if (fillBuffer() < 0)
return -1;
if(isOnBoundary())
return -1; // boundary
return buffer[buf_pos++];
}
/**
* Fills the given array with data from the buffer
*
* @param b is the array that will be filled
* @return the amount of bytes read or -1 if EOF or stream is on a boundary
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads a given length of bytes from the buffer
*
* @param b is the array that will be filled
* @param off is the offset in the array
* @param len is the amount to read
* @return the amount of bytes read or -1 if EOF or stream is on a boundary
*/
public int read(byte b[], int off, int len) throws IOException {
if (fillBuffer() < 0)
return -1; // EOF
if (isOnBoundary())
return -1; // boundary
// The request is larger then the buffer size
int leftover = available();
if (len > leftover)
len = leftover;
// the boundary is in the read range
if (buf_pos < buf_bound_pos && buf_bound_pos < buf_pos+len)
len = buf_bound_pos - buf_pos;
System.arraycopy(buffer, buf_pos, b, off, len);
buf_pos += len;
return len;
}
/**
* @return if the current position in the buffer is a boundary
*/
public boolean isOnBoundary(){
return buf_bound_pos == buf_pos;
}
/**
* Checks if there is more data available after the boundary.
* Note if the data between boundaries are longer than the buffer
* size then this method will return true until the next boundary
* is available in the buffer or the end of the stream.
*
* @return true if there is more data available after the closest boundary.
*/
public boolean hasNext() throws IOException {
if (buf_bound_pos < 0 && fillBuffer() >= 0)
return true; // the boundary not in the buffer?
if (buf_bound_pos >= 0 && buf_bound_pos + boundary.length < buf_end)
return true; // there is more data beyond boundary in the buffer
return false;
}
/**
* Skips over the closest boundary
*/
public void next() throws IOException {
// read data until we find the next boundary or get to the end of the stream
if (buf_bound_pos < 0) {
while (fillBuffer() >= 0 && buf_bound_pos < 0)
buf_pos = buf_end;
}
if (buf_bound_pos >= 0){ // is boundary in buffer?
buf_pos += boundary.length;
findNextBoundary();
}
}
/**
* Skips a specific amounts of bytes in the buffer.
* Note that his method does not check for boundaries
* and it will only skip in the local buffer.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped,
* 0 if it has reach the end of the buffer but does not mean end of stream.
*/
public long skip(long n) throws IOException {
int leftover = available();
if(n > leftover){
buf_pos = buf_end;
return leftover;
}
return buf_pos += n;
}
/**
* Sets the boundary for the stream
*/
public void setBoundary(String b){
this.boundary = b.getBytes();
findNextBoundary(); // redo the search with the new boundary
}
/**
* Sets the boundary for the stream
*/
public void setBoundary(byte[] b){
boundary = new byte[b.length];
System.arraycopy(b, 0, boundary, 0, b.length);
findNextBoundary(); // redo the search with the new boundary
}
/**
* @return an estimate of the number of bytes that can be read (or skipped
* over) from this buffered input stream without blocking.
*/
public int available() throws IOException {
if (super.available() <= 0)
return buf_end - buf_pos; // return the whole stream as there are no more boundaries
else if (buf_end < boundary.length)
return 0; // we need a safety block in case boundary is split
return buf_end - boundary.length - buf_pos;
}
/**
* @return true for BufferedBoundaryInputStream
*/
public boolean markSupported(){
return true;
}
/**
* See {@link InputStream#mark(int)} for details
*
* @param readlimit the amount of data that can be read before
* the mark is invalidated. Note that if the
* readlimit is larger than the buffer size,
* then the buffer size will be used instead.
*/
public void mark(int readlimit) {
buf_mark_limit = readlimit;
buf_mark = buf_pos;
}
/**
* See {@link InputStream#reset()} for details
*
* @exception IOException if the mark is or has been invalidated
*/
public void reset() throws IOException {
if (buf_mark < 0)
throw new IOException("Resetting to invalid mark");
buf_mark_limit = 0;
buf_pos = buf_mark;
buf_mark = -1;
findNextBoundary();
}
/**
* Checks if the buffer needs to be appended with data.
* If so it moves the remaining data to the beginning of the
* buffer and then fills the buffer with data from the source
* stream.
*
* @return the number of new bytes read from the source stream,
* or -1 if the buffer is empty and it is the end of the stream
*/
private int fillBuffer() throws IOException {
// Do we need to fill the buffer
if(buf_pos < buf_end-boundary.length)
return 0;
int leftover = buf_end - buf_pos;
int tmp_pos = buf_pos;
// Mark handling
if (buf_mark > 0) {
// Mark enabled and it has not already been moved to the beginning of the buffer
leftover = buf_end - buf_mark;
buf_pos = buf_mark;
} else if (buf_pos >= buf_mark_limit ||
buf_mark_limit >= buffer.length) {
// we have passed the read limit or read limit is bigger than the buffer, so reset mark
buf_mark = -1;
buf_mark_limit = 0;
}
// Move the end of the buffer to the start to not miss any split boundary
if (leftover > 0 && buf_pos != 0)
System.arraycopy(buffer, buf_pos, buffer, 0, leftover);
// Set new positions
if (buf_mark >= 0){
buf_pos = tmp_pos - buf_mark;
buf_mark = 0;
} else
buf_pos = 0;
buf_end = leftover;
// Copy in new data from the stream
int n = -1;
if (super.available() > 0) { // is there any data available
n = super.read(buffer, buf_end, buffer.length - buf_end);
if (n >= 0)
buf_end = leftover = buf_end + n;
}
// Update boundary position
findNextBoundary();
return ((leftover > 0 && n < 0) ? 0 : n);
}
/**
* Searches for the nearest boundary from the current buffer position
*/
private void findNextBoundary(){
// No need to check for boundary if buffer is smaller than the boundary length
for (int i = buf_pos; i <= buf_end-boundary.length; i++) {
for (int b = 0; b < boundary.length; b++) {
if (buffer[i + b] != boundary[b])
break;
else if (b == boundary.length - 1) {
buf_bound_pos = i;
return;
}
}
}
buf_bound_pos = -1;
}
}