/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.io.buffered;
import java.io.IOException;
import java.io.InputStream;
import org.ws4d.java.constants.Specialchars;
/**
* This class allows the enabling of mark support on an input stream. It is
* possible to read some bytes from the stream without exhausting it.
*/
public class BufferedInputStream extends InputStream {
private static final int DEFAULT_BUFFER_SIZE = 8192;
private static final boolean MARK_SUPPORT = true;
private static final boolean SENDZEROBYTE = false;
/* stream */
private InputStream in = null;
/* mark */
private int mark = -1;
private int marked = -1;
/* pointer */
private int read = -1;
private int last = -1;
/* buffer */
private byte[] buffer;
private boolean markwatch = false;
/* timeout stuff */
private boolean timeout = false;
private boolean reducedtimeout = false;
private int reducedtries = 2;
private int sleep = 10;
private int tries = 500;
/* end */
private boolean closed = false;
private int maxsize = -1;
private int rawread = 0;
/**
* This class allows the enabling of mark support on an input stream. It is
* possible to read some bytes from the stream without exhausting it.
*
* @param in input stream without mark support.
*/
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE, -1);
}
/**
* This class allows the enabling of mark support on an input stream. It is
* possible to read some bytes from the stream without exhausting it.
*
* @param in input stream without mark support.
* @param maxsize do not read more bytes.
*/
public BufferedInputStream(InputStream in, int maxsize) {
this(in, DEFAULT_BUFFER_SIZE, maxsize);
}
/**
* This class allows the enabling of mark support on an input stream. It is
* possible to read some bytes from the stream without exhausting it.
*
* @param in input stream without mark support.
* @param size buffer size.
* @param maxsize do not read more bytes.
*/
public BufferedInputStream(InputStream in, int size, int maxsize) {
if (in instanceof BufferedInputStream) {
throw new RuntimeException("Cannot buffer a BufferedInputStream");
}
if (in == null) {
throw new RuntimeException("Cannot buffer nonexistent stream.");
}
this.in = in;
this.buffer = new byte[size];
this.maxsize = maxsize;
}
// positions
/**
* Sets the read and last byte to the default initialization values.
*/
private void initLegal() {
read = -1;
last = -1;
}
/**
* Returns the last legal position inside the buffer.
*
* @return the last legal position.
*/
private int getLastLegal() {
return last;
}
/**
* Returns the read position inside the buffer.
*
* @return the read position.
*/
private int getReadLegal() {
return read;
}
/**
* Returns the mark position inside the buffer.
*
* @return the mark position.
*/
private int getMarkLegal() {
return mark;
}
/**
* The read position relation the the buffer length.
*
* @return the read position.
*/
private int getReadPosition() {
return getReadLegal() % buffer.length;
}
/**
* The read position relation the the buffer length.
*
* @return the read position.
*/
private int getLastPosition() {
return getLastLegal() % buffer.length;
}
/**
* The read position relation the the buffer length.
*
* @return the read position.
*/
private int getMarkPosition() {
return getMarkLegal() % buffer.length;
}
/**
* Returns the free space available behind the read pointer.
*
* @return the upper free space
*/
private int getUpperFreespace() {
return buffer.length - (getLastPosition() + 1);
}
/**
* Returns the free space available before the read pointer.
*
* @return the lower free space
*/
private int getLowerFreespace() {
return getMarkPosition();
}
/**
* Increase the read pointer.
*
* @param value increase value.
*/
private void incReadLegal(int value) {
read += value;
if (marked == -1) {
mark = read;
}
}
/**
* Returns the number of bytes we can read until the buffer must be filled
* with new bytes.
*
* @return the distance between the read pointer and the last valid byte.
*/
private int getDistance() {
if (getReadLegal() == -1 && getLastLegal() >= 0) {
return getLastLegal();
} else if (getReadLegal() >= 0 && getLastLegal() >= 0) {
return getLastLegal() - getReadLegal();
}
return -1;
}
/**
* Returns <code>true</code> if the stream is closed, <code>false</code>
* otherwise.
*
* @return
*/
private boolean isClosed() {
return closed;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read()
*/
public synchronized int read() throws IOException {
if (isClosed()) return -1;
/*
* Check for mark exceedance.
*/
if (isMarked() && read >= mark + marked) {
cleanMark();
}
/*
* How many bytes left in buffer? Fill buffer if necessary!
*/
if (getDistance() < 0) {
int count = fill();
if (count < 1) {
return -1;
}
}
/*
* Get the value and move the pointer.
*/
int v = buffer[getReadPosition()];
incReadLegal(1);
int i = v & 255;
return i;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read(byte[], int, int)
*/
public synchronized int read(byte b[], int off, int length) throws IOException {
if (isClosed()) return -1;
if ((off | length | (off + length) | (b.length - (off + length))) < 0) {
throw new IndexOutOfBoundsException();
}
int result = -1;
int l = getDistance() + 1;
if (l >= length) {
// We can fill the array directly from the buffer.
System.arraycopy(buffer, getReadPosition(), b, off, length);
incReadLegal(length);
return length;
} else {
// We cannot fill the whole array from the buffer.
if (l > 0) {
// But we can will some...
System.arraycopy(buffer, getReadPosition(), b, off, l);
incReadLegal(l);
}
// calculate the rest (missing bytes).
int rest = length - l;
result = l;
while (rest > 0) {
int count = fill();
if (count < 1) {
if (!SENDZEROBYTE && result == 0) {
return -1;
}
return result;
}
int fill = Math.min(rest, getDistance() + 1);
System.arraycopy(buffer, getReadPosition(), b, off + result, fill);
incReadLegal(fill);
result += fill;
rest -= fill;
}
return result;
}
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read(byte[])
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/*
* (non-Javadoc)
* @see java.io.InputStream#reset()
*/
public synchronized void reset() throws IOException {
if (isClosed()) return;
if (!MARK_SUPPORT) {
throw new IOException("Mark/reset not supported");
}
/*
* If mark was set, reset the read pointer
*/
if (isMarked()) {
read = mark;
}
}
/*
* (non-Javadoc)
* @see java.io.InputStream#markSupported()
*/
public boolean markSupported() {
return MARK_SUPPORT;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#available()
*/
public int available() throws IOException {
if (isClosed()) return 0;
int raw = ((mark == -1) ? read : mark);
int d = (last == -1) ? 0 : last - raw;
int i = in.available();
/*
* calc byte amount, if maxsize is set
*/
if (maxsize > -1) {
if (i > maxsize || d > maxsize) {
if (raw < 0) return maxsize;
return (maxsize - raw);
}
}
return (d < i) ? i : d;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#mark(int)
*/
public synchronized void mark(int readAheadLimit) {
if (isClosed()) return;
if (!MARK_SUPPORT) return;
if (readAheadLimit > buffer.length) {
throw new IndexOutOfBoundsException("Cannot mark beyond the buffer size of " + buffer.length);
}
mark = read;
marked = readAheadLimit;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#close()
*/
public void close() throws IOException {
if (in == null) {
throw new IOException("No stream found");
}
cleanMark();
in.close();
closed = true;
buffer = null;
}
/**
* Returns <code>true</code> if the stream has a mark set,
* <code>false</code> otherwise.
*
* @return <code>true</code> if the stream has a mark set,
* <code>false</code> otherwise.
*/
public boolean isMarked() {
if (!MARK_SUPPORT) return false;
return (marked > -1 && mark > -1);
}
/**
* Allows the stream to throw a MarkReachedException. This Exception will be
* thrown if someone reads until the set mark.
*
* @param mode <code>true</code> to allow exception.
*/
public synchronized void setMarkWatchMode(boolean mode) {
markwatch = mode;
}
/**
* Is mark watched?
*
* @return <code>true</code> if mark is watched, <code>false</code>
* otherwise.
*/
public synchronized boolean isMarkWatched() {
return markwatch;
}
/**
* Is timeout mode enabled?
*
* @return <code>true</code> if this stream is in timeout mode,
* <code>false</code> otherwise.
*/
public boolean isTimeoutMode() {
return timeout;
}
/**
* Set the timeout mode.
*
* @param mode <code>true</code> enables timeout mode, <code>false</code>
* disables it.
*/
public void setTimeoutMode(boolean mode) {
timeout = mode;
}
/**
* Returns the inner input stream.
*
* @return the input stream.
*/
protected InputStream getInputStream() {
return in;
}
/**
* Resets the marked position.
*/
private void cleanMark() throws MarkReachedException {
if (markwatch && isMarked()) {
// SCREAM! if necessary!
throw new MarkReachedException("mark=" + mark + "/" + marked + ", read=" + read);
}
mark = -1;
marked = -1;
}
/**
* Fill the buffer with data from stream.
*
* @return count of filled bytes.
*/
private int fill() throws IOException {
int c = 0;
// calculate correct positions
// int raw = ((mark == -1) ? read : mark);
if (getDistance() < 0 && !isMarked()) {
/*
* if all bytes are read, we can overwrite the internal buffer.
*/
buffer = new byte[buffer.length];
initLegal();
c = readInternal(buffer, 0, buffer.length);
if (c >= 1) {
last = c - 1;
read = 0;
}
} else if (getDistance() < 0 && isMarked()) {
int l = getLowerFreespace();
int u = getUpperFreespace();
if (l > 0) {
c += readInternal(buffer, 0, l);
}
if (u > 0) {
c += readInternal(buffer, getLastPosition() + 1, u);
}
if (l == 0 && u == 0) {
// no free space? but distance below 0?
c += readInternal(buffer, 0, buffer.length);
}
last += c;
}
return c;
}
/**
* Read bytes from stream into the buffer.
*
* @param b buffer to read the bytes in.
* @param off buffer offset.
* @param len read length.
* @return read bytes count.
*/
private int readInternal(byte[] b, int off, int len) throws IOException {
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
}
if (isClosed()) {
return -1;
}
/*
* Correct the length. Never read more bytes then preset.
*/
if (maxsize != -1 && (rawread + len) > maxsize) {
len = maxsize - rawread;
}
int available = in.available();
if (available > 0) {
reducedtimeout = true;
}
/*
* Timeout mode: Check in.available() until some data is received.
*/
int tryCount = 0;
int tryBackup = tries;
if (isTimeoutMode()) {
if (reducedtimeout) {
tryBackup = tries;
tries = reducedtries;
}
while ((available == 0) && (tryCount < tries)) {
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
throw new IOException("Stream read interrupted");
}
available = in.available();
tryCount++;
}
if (available > 0) {
reducedtimeout = true;
reducedtries = (tryCount > 0) ? tryCount : 1;
reducedtries = (reducedtries < 50) ? 50 : reducedtries;
}
if (reducedtimeout) {
tries = tryBackup;
}
// time out?
if (tryCount >= tries) {
return -1;
}
}
/*
* Cannot determinate amount of bytes from underlying input stream. Read
* at least one byte. This is a blocking read.
*/
if (available == 0) {
int v = in.read();
if (v == -1) {
return -1;
}
rawread++;
/*
* Put the read byte into the buffer and return the read length (1).
*/
b[off] = (byte) (v & 255);
return 1;
}
/*
* Calculate amount to read.
*/
int count = (available < (b.length - off)) ? available : (b.length - off);
count = (count > len) ? len : count;
if (count == 0) {
return 0;
}
/*
* Read from the REAL stream.
*/
int l = in.read(b, off, count);
rawread += l;
// String s = new String(b, 0, l);
// System.out.write(b, 0, l);
return l;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public synchronized String toString() {
return printBuffer(buffer);
}
/**
* Creates a <code>String</code> representation of the given byte buffer.
*
* @param b byte buffer to make <code>String</code> from.
* @return the byte buffer as <code>String</code>.
*/
private String printBuffer(byte[] b) {
StringBuffer sb = new StringBuffer();
sb.append(read + "/" + last);
sb.append((char) Specialchars.CR);
sb.append((char) Specialchars.LF);
// sb.append("[");
// for (int i = 0; i < b.length; i++) {
// sb.append(i + "=>" + b[i]);
// if (i < (b.length - 1)) {
// sb.append(", ");
// }
// }
// sb.append("]");
for (int i = 0; i < b.length; i++) {
sb.append((char) b[i]);
}
return sb.toString();
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((in == null) ? 0 : in.hashCode());
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
BufferedInputStream other = (BufferedInputStream) obj;
if (in == null) {
if (other.in != null) return false;
} else if (!in.equals(other.in)) return false;
return true;
}
}