001: //
002: // Borrowed from org.apache.xerces.impl.XMLEntityManager,
003: // slightly modified and commented out. -AK
004: //
005: // This class wraps the byte inputstreams we're presented with.
006: // We need it because java.io.InputStreams don't provide
007: // functionality to reread processed bytes, and they have a habit
008: // of reading more than one character when you call their read()
009: // methods. This means that, once we discover the true (declared)
010: // encoding of a document, we can neither backtrack to read the
011: // whole doc again nor start reading where we are with a new
012: // reader.
013: //
014: // This class allows rewinding an inputStream by allowing a mark
015: // to be set, and the stream reset to that position. <strong>The
016: // class assumes that it needs to read one character per
017: // invocation when it's read() method is inovked, but uses the
018: // underlying InputStream's read(char[], offset length) method--it
019: // won't buffer data read this way!</strong>
020: //
021: // @task TODO: How about implementing an ability to completely
022: // disable buffering performed by this stream?
023: // It is very unlikely that someone will read data from
024: // the stream byte by byte with <code>read()</code>
025: // method, but if they do, the whole content will
026: // be unconditionally stored in internal buffer, resulting
027: // in additional (and most probably vain) memory impact.
028: //
029: // @author Neil Graham, IBM
030: // @author Glenn Marcy, IBM
031: //
032: package org.geoserver.ows.util;
033:
034: import java.io.IOException;
035: import java.io.InputStream;
036:
037: public class RewindableInputStream extends InputStream {
038: /**
039: * Default buffer size before we've finished with the XMLDecl:
040: * I think the name should be left unchanged to give a hint for
041: * possible use of this class :)
042: */
043: public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
044:
045: /**
046: * Tells whether <code>read(byte[], int, int)</code> method
047: * is allowed to read multiple bytes beyond the internal buffer
048: * (<code>true</code>) or not (<code>true</code> is the default)
049: */
050: protected boolean fMayReadChunks;
051:
052: /**
053: * Source input stream we are wrapping with rewindable one.
054: */
055: protected InputStream fInputStream;
056:
057: /**
058: * Internal buffer of bytes already read from source input stream.
059: * Allows to access the same byte more than once.
060: */
061: protected byte[] fData;
062:
063: /**
064: * Position in the stream that to which stream pointer can be reset
065: * with <code>rewind</code> method invocation.
066: */
067: protected int fStartOffset;
068:
069: /**
070: * Position where the end of underlying stream was encountered. Potentially
071: * in <code>RewindableInputStream</code> instance stream's "end" could be
072: * reached more than once, so it is a good thing to know where original
073: * stream ended to avoid <code>IOExceptions</code>.
074: */
075: protected int fEndOffset;
076:
077: /**
078: * Offset of the next byte to be read from the stream relative to
079: * the beginning of the stream (and <code>fData</code> array as well)
080: */
081: protected int fOffset;
082:
083: /**
084: * Number of read bytes currently stored in <code>fData</code> buffer.
085: * Size of the buffer itself can be greater than this value, obviously.
086: */
087: protected int fLength;
088:
089: /**
090: * Offset of the "marked" position in the stream relative to its beginning.
091: */
092: protected int fMark;
093:
094: /**
095: * Creates new <code>RewindableInputStream</code> object with internal
096: * buffer of default size and default value of chunked reading flag (which
097: * is _currently_ <code>true</code>).
098: *
099: * @param is InputStream that needs basic reset/rewind functionality.
100: */
101: public RewindableInputStream(InputStream is) {
102: this (is, true, DEFAULT_XMLDECL_BUFFER_SIZE);
103: }
104:
105: /**
106: * Creates new RewindableInputStream with internal buffer of specified size
107: * and no chunk reading beyound the buffer limits allowed.
108: *
109: * @param is InputStream that needs some reset/rewind functionality.
110: *
111: * @param chunkedMode See the <code>RewindableInputStream(InputStream,
112: * boolean, int)</code> constructor description.
113: */
114: public RewindableInputStream(InputStream is, boolean chunkedMode) {
115: this (is, chunkedMode, DEFAULT_XMLDECL_BUFFER_SIZE);
116: }
117:
118: /**
119: * Primary constructor that allows to specify all parameters exlicitly
120: * affecting class work (initial size of the internal buffer and
121: * chunk read mode).
122: *
123: * @param is InputStream that needs some reset/rewind functionality.
124: *
125: * @param chunkedMode
126: *
127: * Initial value of <code>fMayReadChunks</code> flag which determines
128: * whether multiple bytes can be read from the underlying stream in
129: * single reading operation or not. This value can be changed using
130: * <code>setChunkedMode</code> (or its aliases). For specific
131: * purpose of inferring encoding/charset of XML document typical
132: * usage policy is to disable chunked reads while obtaining XML
133: * declaration and then enable it to speed up reading the rest of
134: * document.
135: *
136: * @param initialSize Initial size of the internal buffer array.
137: */
138: public RewindableInputStream(InputStream is, boolean chunkedMode,
139: int initialSize) {
140: if (0 >= initialSize) {
141: initialSize = DEFAULT_XMLDECL_BUFFER_SIZE;
142: }
143:
144: fData = new byte[initialSize];
145:
146: fInputStream = is;
147: fStartOffset = 0;
148: fMayReadChunks = chunkedMode;
149: fEndOffset = -1;
150: fOffset = 0;
151: fLength = 0;
152: fMark = 0;
153: }
154:
155: /**
156: * Sets the position somewhere in the stream to which the stream pointer
157: * will be reset after <code>rewind</code> invocation. By default this
158: * position is the beginning of the stream.
159: *
160: * @param offset New value for "fStartOffset".
161: */
162: public void setStartOffset(int offset) {
163: fStartOffset = offset;
164: }
165:
166: /**
167: * Allows to change the behavior of the stream regarding chunked reading
168: * at runtime. If you allowed chunked reading and then read some data from
169: * the stream, you better forget about <code>reset</code>ting or
170: * <code>rewind</code>ing it after that.
171: *
172: * @param chunkedMode New value for <code>fMayReadChunks</code>.
173: */
174: public void setChunkedMode(boolean chunkedMode) {
175: fMayReadChunks = chunkedMode;
176: }
177:
178: /**
179: * More conscious alias for <code>setChunkedMode(true)</code>. While last
180: * method is a general purpose mutator, code may look a bit more clear if
181: * you use specialized methods to enable/disable chunk read mode.
182: */
183: public void enableChunkedMode() {
184: fMayReadChunks = true;
185: }
186:
187: /**
188: * More conscious alias for <code>setChunkedMode(false)</code>. While last
189: * method is a general purpose mutator, code may look a bit more clear if
190: * you use specialized methods to enable/disable chunk read mode.
191: */
192: public void disableChunkedMode() {
193: fMayReadChunks = false;
194: }
195:
196: /**
197: * Quickly reset stream pointer to the beginning of the stream or to
198: * position which offset was specified during the last
199: * <code>setStartOffset</code> call.
200: */
201: public void rewind() {
202: fOffset = fStartOffset;
203: }
204:
205: /**
206: * Reads next byte from this stream. This byte is either being read from
207: * underlying InputStream or taken from the internal buffer in case it was
208: * already read at some point before.
209: *
210: * @return Next byte of data or <code>-1</code> if end of stream is reached.
211: *
212: * @throws IOException in case of any I/O errors.
213: */
214: public int read() throws IOException {
215: int b = 0;
216:
217: // Byte to be read is already in out buffer, simply returning it
218: if (fOffset < fLength) {
219: return fData[fOffset++] & 0xff;
220: }
221:
222: /*
223: * End of the stream is reached.
224: * I also believe that in certain cases fOffset can point to the
225: * position after the end of stream, for example, after invalid
226: * `setStartOffset()` call followed by `rewind()`.
227: * This situation is not handled currently.
228: */
229: if (fOffset == fEndOffset) {
230: return -1;
231: }
232:
233: /*
234: * Ok, we should actually read data from underlying stream, but
235: * first it will be good to check if buffer array should be
236: * expanded. Each time buffer size is doubled.
237: */
238: if (fOffset == fData.length) {
239: byte[] newData = new byte[fOffset << 1];
240: System.arraycopy(fData, 0, newData, 0, fOffset);
241: fData = newData;
242: }
243:
244: // Reading byte from the underlying stream, storing it in buffer and
245: // then returning it.
246: b = fInputStream.read();
247:
248: if (b == -1) {
249: fEndOffset = fOffset;
250:
251: return -1;
252: }
253:
254: fData[fLength++] = (byte) b;
255: fOffset++;
256:
257: return b & 0xff;
258: } // END read()
259:
260: /**
261: * Reads up to len bytes of data from the input stream into an array of
262: * bytes. In its current implementation it cannot return more bytes than
263: * left in the buffer if in "non-chunked" mode
264: * (<code>fMayReadChunks == false</code>). After reaching the end of
265: * the buffer, each invocation of this method will read exactly 1 byte
266: * then. In "chunked" mode this method <em>may</em> return more than 1
267: * byte, but it doesn't buffer the result.
268: *
269: * <p>From the other hand, for the task of reading xml declaration, such
270: * behavior may be desirable, as we probably don't need reset/rewind
271: * functionality after we finished with charset deduction. It is good
272: * idea to call <code>enableChunkedMode</code> after that, in order to
273: * improve perfomance and lessen memoery consumption when reading the rest
274: * of the data.
275: *
276: * @return Total number of bytes actually read or <code>-1</code> if end
277: * of stream has been reached.
278: *
279: * @throws IOException when an I/O error occurs while reading data
280: *
281: * @throws IndexOutOfBoundsException in case of invalid <code>off</code>,
282: * <code>len</code> and <code>b.length</code> combination
283: */
284: public int read(byte[] b, int off, int len) throws IOException {
285: if (null == b) {
286: throw new NullPointerException(
287: "Destination byte array is null.");
288: } else if (0 == len) {
289: return 0;
290: } else if ((b.length < off) || (b.length < (off + len))
291: || (0 > off) || (0 > len)) {
292: throw new IndexOutOfBoundsException();
293: }
294:
295: int bytesLeft = fLength - fOffset;
296:
297: /*
298: * There is no more bytes in the buffer. We either reading 1 byte
299: * from underlying InputStream and saving it in the buffer, or
300: * getting more bytes without saving them, depending on the value
301: * of `fMayReadChunks` field.
302: */
303: if (bytesLeft == 0) {
304: if (fOffset == fEndOffset) {
305: return -1;
306: }
307:
308: // better get some more for the voracious reader...
309: if (fMayReadChunks) {
310: // Hmm, this can be buffered in theory. But in many
311: // cases this would be undesirable, so let it be as it is.
312: return fInputStream.read(b, off, len);
313: }
314:
315: int returnedVal = read();
316:
317: if (returnedVal == -1) {
318: fEndOffset = fOffset;
319:
320: return -1;
321: }
322:
323: b[off] = (byte) returnedVal;
324:
325: return 1;
326: }
327:
328: /*
329: * In non-chunked mode we shouldn't give out more bytes then left
330: * in the buffer.
331: */
332: if (fMayReadChunks) {
333: // Count of bytes to get form buffer
334: int readFromBuffer = (len < bytesLeft) ? len : bytesLeft;
335:
336: System.arraycopy(fData, fOffset, b, off, readFromBuffer);
337:
338: int readFromStream = 0;
339:
340: if (len > bytesLeft) {
341: readFromStream = fInputStream.read(b, off + bytesLeft,
342: len - bytesLeft);
343: }
344:
345: fOffset += readFromBuffer;
346:
347: return readFromBuffer
348: + ((-1 == readFromStream) ? 0 : readFromStream);
349: } else {
350: //
351: // This will prevent returning more bytes than the remainder of
352: // the buffer array.
353: if (len > bytesLeft) {
354: len = bytesLeft;
355: }
356:
357: System.arraycopy(fData, fOffset, b, off, len);
358:
359: fOffset += len;
360:
361: return len;
362: }
363: } // END read(byte[], int, int)
364:
365: /**
366: * Skips over and discards <code>n</code> bytes of data from this input
367: * stream. The skip method may, for a variety of reasons, end up skipping
368: * over some smaller number of bytes, possibly <code>0</code>. The actual
369: * number of bytes skipped is returned. If <code>n</code> is negative, no
370: * bytes are skipped.
371: *
372: * @param n Number of bytes to be skipped.
373: *
374: * @return Number of bytes actually skipped.
375: *
376: * @throws IOException if an I/O error occurs.
377: */
378: public long skip(long n) throws IOException {
379: int bytesLeft;
380:
381: if (n <= 0) {
382: return 0;
383: }
384:
385: bytesLeft = fLength - fOffset;
386:
387: // If end of buffer is reached, using `skip()` of the underlying input
388: // stream
389: if (bytesLeft == 0) {
390: if (fOffset == fEndOffset) {
391: return 0;
392: }
393:
394: return fInputStream.skip(n);
395: }
396:
397: // Quickly "skipping" bytes in the buffer by modifying its pointer.
398: if (n <= bytesLeft) {
399: fOffset += n;
400:
401: return n;
402: }
403:
404: fOffset += bytesLeft;
405:
406: if (fOffset == fEndOffset) {
407: return bytesLeft;
408: }
409:
410: n -= bytesLeft;
411:
412: return fInputStream.skip(n) + bytesLeft;
413: } // END skip(long)
414:
415: /**
416: * Returns the number of bytes that can be read (or skipped over) from this
417: * input stream without blocking by the next caller of a method for this
418: * input stream. For <code>RewindableInputStream</code> this can be:
419: *
420: * <ul>
421: * <li>
422: * Number of unread bytes in the <code>fData</code> buffer, i.e. those
423: * between current position (fOffset) and total bytes quantity in the
424: * buffer (fLength).
425: * </li>
426: * <li>
427: * Result of underlying InputStream's <code>available</code> call
428: * if there are no unread bytes in the buffer.
429: * </li>
430: * <li>
431: * <code>-1</code> if end of stream is reached.
432: * </li>
433: * </ul>
434: *
435: * @return the number of bytes that can be read from this input stream
436: * without blocking.
437: *
438: * @throws IOException when an I/O error occurs.
439: */
440: public int available() throws IOException {
441: int bytesLeft = fLength - fOffset;
442:
443: if (bytesLeft == 0) {
444: // Again, the same thing as in `read()`. Do we need to throw
445: // an exception if fOffset > fEndOffset???
446: if (fOffset == fEndOffset) {
447: return -1;
448: }
449:
450: /*
451: * In a manner of speaking, when this class isn't permitting more
452: * than one byte at a time to be read, it is "blocking". The
453: * available() method should indicate how much can be read without
454: * blocking, so while we're in this mode, it should only indicate
455: * that bytes in its buffer are available; otherwise, the result of
456: * available() on the underlying InputStream is appropriate.
457: */
458: return fMayReadChunks ? fInputStream.available() : 0;
459: }
460:
461: return bytesLeft;
462: }
463:
464: /**
465: * Sets a mark to the current position in the stream.
466: *
467: * @param howMuch Not used in this implementation I guess.
468: */
469: public void mark(int howMuch) {
470: fMark = fOffset;
471: }
472:
473: /**
474: * Returns stream pointer to the position previously remembered
475: * using <code>mark</code> method (or to beginning of the stream,
476: * if there were no <code>mark</code> method calls).
477: */
478: public void reset() {
479: fOffset = fMark;
480: }
481:
482: /**
483: * Tells that this stream supports mark/reset capability.
484: * This one definitely supports it :)
485: *
486: * @return <code>true</code> if this stream instance supports the mark
487: * and reset methods; <code>false</code> otherwise.
488: */
489: public boolean markSupported() {
490: return true;
491: }
492:
493: /**
494: * Closes underlying byte stream.
495: *
496: * @throws IOException if an I/O error occurs.
497: */
498: public void close() throws IOException {
499: if (fInputStream != null) {
500: fInputStream.close();
501: fInputStream = null;
502: fData = null;
503: }
504: }
505: } // end of RewindableInputStream class
|