// // Borrowed from org.apache.xerces.impl.XMLEntityManager, // slightly modified and commented out. -AK // // This class wraps the byte inputstreams we're presented with. // We need it because java.io.InputStreams don't provide // functionality to reread processed bytes, and they have a habit // of reading more than one character when you call their read() // methods. This means that, once we discover the true (declared) // encoding of a document, we can neither backtrack to read the // whole doc again nor start reading where we are with a new // reader. // // This class allows rewinding an inputStream by allowing a mark // to be set, and the stream reset to that position. <strong>The // class assumes that it needs to read one character per // invocation when it's read() method is inovked, but uses the // underlying InputStream's read(char[], offset length) method--it // won't buffer data read this way!</strong> // // @task TODO: How about implementing an ability to completely // disable buffering performed by this stream? // It is very unlikely that someone will read data from // the stream byte by byte with <code>read()</code> // method, but if they do, the whole content will // be unconditionally stored in internal buffer, resulting // in additional (and most probably vain) memory impact. // // @author Neil Graham, IBM // @author Glenn Marcy, IBM // package org.geoserver.ows.util; import java.io.IOException; import java.io.InputStream; public class RewindableInputStream extends InputStream { /** * Default buffer size before we've finished with the XMLDecl: * I think the name should be left unchanged to give a hint for * possible use of this class :) */ public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64; /** * Tells whether <code>read(byte[], int, int)</code> method * is allowed to read multiple bytes beyond the internal buffer * (<code>true</code>) or not (<code>true</code> is the default) */ protected boolean fMayReadChunks; /** * Source input stream we are wrapping with rewindable one. */ protected InputStream fInputStream; /** * Internal buffer of bytes already read from source input stream. * Allows to access the same byte more than once. */ protected byte[] fData; /** * Position in the stream that to which stream pointer can be reset * with <code>rewind</code> method invocation. */ protected int fStartOffset; /** * Position where the end of underlying stream was encountered. Potentially * in <code>RewindableInputStream</code> instance stream's "end" could be * reached more than once, so it is a good thing to know where original * stream ended to avoid <code>IOExceptions</code>. */ protected int fEndOffset; /** * Offset of the next byte to be read from the stream relative to * the beginning of the stream (and <code>fData</code> array as well) */ protected int fOffset; /** * Number of read bytes currently stored in <code>fData</code> buffer. * Size of the buffer itself can be greater than this value, obviously. */ protected int fLength; /** * Offset of the "marked" position in the stream relative to its beginning. */ protected int fMark; /** * Creates new <code>RewindableInputStream</code> object with internal * buffer of default size and default value of chunked reading flag (which * is _currently_ <code>true</code>). * * @param is InputStream that needs basic reset/rewind functionality. */ public RewindableInputStream(InputStream is) { this(is, true, DEFAULT_XMLDECL_BUFFER_SIZE); } /** * Creates new RewindableInputStream with internal buffer of specified size * and no chunk reading beyound the buffer limits allowed. * * @param is InputStream that needs some reset/rewind functionality. * * @param chunkedMode See the <code>RewindableInputStream(InputStream, * boolean, int)</code> constructor description. */ public RewindableInputStream(InputStream is, boolean chunkedMode) { this(is, chunkedMode, DEFAULT_XMLDECL_BUFFER_SIZE); } /** * Primary constructor that allows to specify all parameters exlicitly * affecting class work (initial size of the internal buffer and * chunk read mode). * * @param is InputStream that needs some reset/rewind functionality. * * @param chunkedMode * * Initial value of <code>fMayReadChunks</code> flag which determines * whether multiple bytes can be read from the underlying stream in * single reading operation or not. This value can be changed using * <code>setChunkedMode</code> (or its aliases). For specific * purpose of inferring encoding/charset of XML document typical * usage policy is to disable chunked reads while obtaining XML * declaration and then enable it to speed up reading the rest of * document. * * @param initialSize Initial size of the internal buffer array. */ public RewindableInputStream(InputStream is, boolean chunkedMode, int initialSize) { if (0 >= initialSize) { initialSize = DEFAULT_XMLDECL_BUFFER_SIZE; } fData = new byte[initialSize]; fInputStream = is; fStartOffset = 0; fMayReadChunks = chunkedMode; fEndOffset = -1; fOffset = 0; fLength = 0; fMark = 0; } /** * Sets the position somewhere in the stream to which the stream pointer * will be reset after <code>rewind</code> invocation. By default this * position is the beginning of the stream. * * @param offset New value for "fStartOffset". */ public void setStartOffset(int offset) { fStartOffset = offset; } /** * Allows to change the behavior of the stream regarding chunked reading * at runtime. If you allowed chunked reading and then read some data from * the stream, you better forget about <code>reset</code>ting or * <code>rewind</code>ing it after that. * * @param chunkedMode New value for <code>fMayReadChunks</code>. */ public void setChunkedMode(boolean chunkedMode) { fMayReadChunks = chunkedMode; } /** * More conscious alias for <code>setChunkedMode(true)</code>. While last * method is a general purpose mutator, code may look a bit more clear if * you use specialized methods to enable/disable chunk read mode. */ public void enableChunkedMode() { fMayReadChunks = true; } /** * More conscious alias for <code>setChunkedMode(false)</code>. While last * method is a general purpose mutator, code may look a bit more clear if * you use specialized methods to enable/disable chunk read mode. */ public void disableChunkedMode() { fMayReadChunks = false; } /** * Quickly reset stream pointer to the beginning of the stream or to * position which offset was specified during the last * <code>setStartOffset</code> call. */ public void rewind() { fOffset = fStartOffset; } /** * Reads next byte from this stream. This byte is either being read from * underlying InputStream or taken from the internal buffer in case it was * already read at some point before. * * @return Next byte of data or <code>-1</code> if end of stream is reached. * * @throws IOException in case of any I/O errors. */ public int read() throws IOException { int b = 0; // Byte to be read is already in out buffer, simply returning it if (fOffset < fLength) { return fData[fOffset++] & 0xff; } /* * End of the stream is reached. * I also believe that in certain cases fOffset can point to the * position after the end of stream, for example, after invalid * `setStartOffset()` call followed by `rewind()`. * This situation is not handled currently. */ if (fOffset == fEndOffset) { return -1; } /* * Ok, we should actually read data from underlying stream, but * first it will be good to check if buffer array should be * expanded. Each time buffer size is doubled. */ if (fOffset == fData.length) { byte[] newData = new byte[fOffset << 1]; System.arraycopy(fData, 0, newData, 0, fOffset); fData = newData; } // Reading byte from the underlying stream, storing it in buffer and // then returning it. b = fInputStream.read(); if (b == -1) { fEndOffset = fOffset; return -1; } fData[fLength++] = (byte) b; fOffset++; return b & 0xff; } // END read() /** * Reads up to len bytes of data from the input stream into an array of * bytes. In its current implementation it cannot return more bytes than * left in the buffer if in "non-chunked" mode * (<code>fMayReadChunks == false</code>). After reaching the end of * the buffer, each invocation of this method will read exactly 1 byte * then. In "chunked" mode this method <em>may</em> return more than 1 * byte, but it doesn't buffer the result. * * <p>From the other hand, for the task of reading xml declaration, such * behavior may be desirable, as we probably don't need reset/rewind * functionality after we finished with charset deduction. It is good * idea to call <code>enableChunkedMode</code> after that, in order to * improve perfomance and lessen memoery consumption when reading the rest * of the data. * * @return Total number of bytes actually read or <code>-1</code> if end * of stream has been reached. * * @throws IOException when an I/O error occurs while reading data * * @throws IndexOutOfBoundsException in case of invalid <code>off</code>, * <code>len</code> and <code>b.length</code> combination */ public int read(byte[] b, int off, int len) throws IOException { if (null == b) { throw new NullPointerException("Destination byte array is null."); } else if (0 == len) { return 0; } else if ((b.length < off) || (b.length < (off + len)) || (0 > off) || (0 > len)) { throw new IndexOutOfBoundsException(); } int bytesLeft = fLength - fOffset; /* * There is no more bytes in the buffer. We either reading 1 byte * from underlying InputStream and saving it in the buffer, or * getting more bytes without saving them, depending on the value * of `fMayReadChunks` field. */ if (bytesLeft == 0) { if (fOffset == fEndOffset) { return -1; } // better get some more for the voracious reader... if (fMayReadChunks) { // Hmm, this can be buffered in theory. But in many // cases this would be undesirable, so let it be as it is. return fInputStream.read(b, off, len); } int returnedVal = read(); if (returnedVal == -1) { fEndOffset = fOffset; return -1; } b[off] = (byte) returnedVal; return 1; } /* * In non-chunked mode we shouldn't give out more bytes then left * in the buffer. */ if (fMayReadChunks) { // Count of bytes to get form buffer int readFromBuffer = (len < bytesLeft) ? len : bytesLeft; System.arraycopy(fData, fOffset, b, off, readFromBuffer); int readFromStream = 0; if (len > bytesLeft) { readFromStream = fInputStream.read(b, off + bytesLeft, len - bytesLeft); } fOffset += readFromBuffer; return readFromBuffer + ((-1 == readFromStream) ? 0 : readFromStream); } else { // // This will prevent returning more bytes than the remainder of // the buffer array. if (len > bytesLeft) { len = bytesLeft; } System.arraycopy(fData, fOffset, b, off, len); fOffset += len; return len; } } // END read(byte[], int, int) /** * Skips over and discards <code>n</code> bytes of data from this input * stream. The skip method may, for a variety of reasons, end up skipping * over some smaller number of bytes, possibly <code>0</code>. The actual * number of bytes skipped is returned. If <code>n</code> is negative, no * bytes are skipped. * * @param n Number of bytes to be skipped. * * @return Number of bytes actually skipped. * * @throws IOException if an I/O error occurs. */ public long skip(long n) throws IOException { int bytesLeft; if (n <= 0) { return 0; } bytesLeft = fLength - fOffset; // If end of buffer is reached, using `skip()` of the underlying input // stream if (bytesLeft == 0) { if (fOffset == fEndOffset) { return 0; } return fInputStream.skip(n); } // Quickly "skipping" bytes in the buffer by modifying its pointer. if (n <= bytesLeft) { fOffset += n; return n; } fOffset += bytesLeft; if (fOffset == fEndOffset) { return bytesLeft; } n -= bytesLeft; return fInputStream.skip(n) + bytesLeft; } // END skip(long) /** * Returns the number of bytes that can be read (or skipped over) from this * input stream without blocking by the next caller of a method for this * input stream. For <code>RewindableInputStream</code> this can be: * * <ul> * <li> * Number of unread bytes in the <code>fData</code> buffer, i.e. those * between current position (fOffset) and total bytes quantity in the * buffer (fLength). * </li> * <li> * Result of underlying InputStream's <code>available</code> call * if there are no unread bytes in the buffer. * </li> * <li> * <code>-1</code> if end of stream is reached. * </li> * </ul> * * @return the number of bytes that can be read from this input stream * without blocking. * * @throws IOException when an I/O error occurs. */ public int available() throws IOException { int bytesLeft = fLength - fOffset; if (bytesLeft == 0) { // Again, the same thing as in `read()`. Do we need to throw // an exception if fOffset > fEndOffset??? if (fOffset == fEndOffset) { return -1; } /* * In a manner of speaking, when this class isn't permitting more * than one byte at a time to be read, it is "blocking". The * available() method should indicate how much can be read without * blocking, so while we're in this mode, it should only indicate * that bytes in its buffer are available; otherwise, the result of * available() on the underlying InputStream is appropriate. */ return fMayReadChunks ? fInputStream.available() : 0; } return bytesLeft; } /** * Sets a mark to the current position in the stream. * * @param howMuch Not used in this implementation I guess. */ public void mark(int howMuch) { fMark = fOffset; } /** * Returns stream pointer to the position previously remembered * using <code>mark</code> method (or to beginning of the stream, * if there were no <code>mark</code> method calls). */ public void reset() { fOffset = fMark; } /** * Tells that this stream supports mark/reset capability. * This one definitely supports it :) * * @return <code>true</code> if this stream instance supports the mark * and reset methods; <code>false</code> otherwise. */ public boolean markSupported() { return true; } /** * Closes underlying byte stream. * * @throws IOException if an I/O error occurs. */ public void close() throws IOException { if (fInputStream != null) { fInputStream.close(); fInputStream = null; fData = null; } } } // end of RewindableInputStream class