/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.io; import java.util.Arrays; /** * Wraps an existing {@link InputStream} and adds functionality to "push back" * bytes that have been read, so that they can be read again. Parsers may find * this useful. The number of bytes which may be pushed back can be specified * during construction. If the buffer of pushed back bytes is empty, bytes are * read from the underlying input stream. */ public class PushbackInputStream extends FilterInputStream { /** * The buffer that contains pushed-back bytes. */ protected byte[] buf; /** * The current position within {@code buf}. A value equal to * {@code buf.length} indicates that no bytes are available. A value of 0 * indicates that the buffer is full. */ protected int pos; /** * Constructs a new {@code PushbackInputStream} with the specified input * stream as source. The size of the pushback buffer is set to the default * value of 1 byte. * * <p><strong>Warning:</strong> passing a null source creates an invalid * {@code PushbackInputStream}. All read operations on such a stream will * fail. * * @param in * the source input stream. */ public PushbackInputStream(InputStream in) { super(in); buf = (in == null) ? null : new byte[1]; pos = 1; } /** * Constructs a new {@code PushbackInputStream} with {@code in} as source * input stream. The size of the pushback buffer is set to {@code size}. * * <p><strong>Warning:</strong> passing a null source creates an invalid * {@code PushbackInputStream}. All read operations on such a stream will * fail. * * @param in * the source input stream. * @param size * the size of the pushback buffer. * @throws IllegalArgumentException * if {@code size} is negative. */ public PushbackInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("size <= 0"); } buf = (in == null) ? null : new byte[size]; pos = size; } @Override public int available() throws IOException { if (buf == null) { throw new IOException(); } return buf.length - pos + in.available(); } /** * Closes this stream. This implementation closes the source stream * and releases the pushback buffer. * * @throws IOException * if an error occurs while closing this stream. */ @Override public void close() throws IOException { if (in != null) { in.close(); in = null; buf = null; } } /** * Indicates whether this stream supports the {@code mark(int)} and * {@code reset()} methods. {@code PushbackInputStream} does not support * them, so it returns {@code false}. * * @return always {@code false}. * @see #mark(int) * @see #reset() */ @Override public boolean markSupported() { return false; } /** * Reads a single byte from this stream and returns it as an integer in the * range from 0 to 255. If the pushback buffer does not contain any * available bytes then a byte from the source input stream is returned. * Blocks until one byte has been read, the end of the source stream is * detected or an exception is thrown. * * @return the byte read or -1 if the end of the source stream has been * reached. * @throws IOException * if this stream is closed or an I/O error occurs while reading * from this stream. */ @Override public int read() throws IOException { if (buf == null) { throw new IOException(); } // Is there a pushback byte available? if (pos < buf.length) { return (buf[pos++] & 0xFF); } // Assume read() in the InputStream will return low-order byte or -1 // if end of stream. return in.read(); } /** * Reads up to {@code byteCount} bytes from this stream and stores them in * the byte array {@code buffer} starting at {@code byteOffset}. Bytes are read * from the pushback buffer first, then from the source stream if more bytes * are required. Blocks until {@code byteCount} bytes have been read, the end of * the source stream is detected or an exception is thrown. Returns the number of bytes read, * or -1 if the end of the source stream has been reached. * * @throws IndexOutOfBoundsException * if {@code byteOffset < 0 || byteCount < 0 || byteOffset + byteCount > buffer.length}. * @throws IOException * if this stream is closed or another I/O error occurs while * reading from this stream. * @throws NullPointerException * if {@code buffer == null}. */ @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { if (buf == null) { throw streamClosed(); } Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount); int copiedBytes = 0, copyLength = 0, newOffset = byteOffset; // Are there pushback bytes available? if (pos < buf.length) { copyLength = (buf.length - pos >= byteCount) ? byteCount : buf.length - pos; System.arraycopy(buf, pos, buffer, newOffset, copyLength); newOffset += copyLength; copiedBytes += copyLength; // Use up the bytes in the local buffer pos += copyLength; } // Have we copied enough? if (copyLength == byteCount) { return byteCount; } int inCopied = in.read(buffer, newOffset, byteCount - copiedBytes); if (inCopied > 0) { return inCopied + copiedBytes; } if (copiedBytes == 0) { return inCopied; } return copiedBytes; } private IOException streamClosed() throws IOException { throw new IOException("PushbackInputStream is closed"); } /** * Skips {@code byteCount} bytes in this stream. This implementation skips bytes * in the pushback buffer first and then in the source stream if necessary. * * @return the number of bytes actually skipped. * @throws IOException * if this stream is closed or another I/O error occurs. */ @Override public long skip(long byteCount) throws IOException { if (in == null) { throw streamClosed(); } if (byteCount <= 0) { return 0; } int numSkipped = 0; if (pos < buf.length) { numSkipped += (byteCount < buf.length - pos) ? byteCount : buf.length - pos; pos += numSkipped; } if (numSkipped < byteCount) { numSkipped += in.skip(byteCount - numSkipped); } return numSkipped; } /** * Pushes all the bytes in {@code buffer} back to this stream. The bytes are * pushed back in such a way that the next byte read from this stream is * buffer[0], then buffer[1] and so on. * <p> * If this stream's internal pushback buffer cannot store the entire * contents of {@code buffer}, an {@code IOException} is thrown. Parts of * {@code buffer} may have already been copied to the pushback buffer when * the exception is thrown. * * @param buffer * the buffer containing the bytes to push back to this stream. * @throws IOException * if the free space in the internal pushback buffer is not * sufficient to store the contents of {@code buffer}. */ public void unread(byte[] buffer) throws IOException { unread(buffer, 0, buffer.length); } /** * Pushes a subset of the bytes in {@code buffer} back to this stream. The * subset is defined by the start position {@code offset} within * {@code buffer} and the number of bytes specified by {@code length}. The * bytes are pushed back in such a way that the next byte read from this * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on. * <p> * If this stream's internal pushback buffer cannot store the selected * subset of {@code buffer}, an {@code IOException} is thrown. Parts of * {@code buffer} may have already been copied to the pushback buffer when * the exception is thrown. * * @param buffer * the buffer containing the bytes to push back to this stream. * @param offset * the index of the first byte in {@code buffer} to push back. * @param length * the number of bytes to push back. * @throws IndexOutOfBoundsException * if {@code offset < 0} or {@code length < 0}, or if * {@code offset + length} is greater than the length of * {@code buffer}. * @throws IOException * if the free space in the internal pushback buffer is not * sufficient to store the selected contents of {@code buffer}. */ public void unread(byte[] buffer, int offset, int length) throws IOException { if (length > pos) { throw new IOException("Pushback buffer full"); } Arrays.checkOffsetAndCount(buffer.length, offset, length); if (buf == null) { throw streamClosed(); } System.arraycopy(buffer, offset, buf, pos - length, length); pos = pos - length; } /** * Pushes the specified byte {@code oneByte} back to this stream. Only the * least significant byte of the integer {@code oneByte} is pushed back. * This is done in such a way that the next byte read from this stream is * {@code (byte) oneByte}. * <p> * If this stream's internal pushback buffer cannot store the byte, an * {@code IOException} is thrown. * * @param oneByte * the byte to push back to this stream. * @throws IOException * if this stream is closed or the internal pushback buffer is * full. */ public void unread(int oneByte) throws IOException { if (buf == null) { throw new IOException(); } if (pos == 0) { throw new IOException("Pushback buffer full"); } buf[--pos] = (byte) oneByte; } /** * Marks the current position in this stream. Setting a mark is not * supported in this class; this implementation does nothing. * * @param readlimit * the number of bytes that can be read from this stream before * the mark is invalidated; this parameter is ignored. */ @Override public void mark(int readlimit) { } /** * Resets this stream to the last marked position. Resetting the stream is * not supported in this class; this implementation always throws an * {@code IOException}. * * @throws IOException * if this method is called. */ @Override public void reset() throws IOException { throw new IOException(); } }