Home | History | Annotate | Download | only in io
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package java.io;
     19 
     20 import java.util.Arrays;
     21 
     22 /**
     23  * Wraps an existing {@link InputStream} and adds functionality to "push back"
     24  * bytes that have been read, so that they can be read again. Parsers may find
     25  * this useful. The number of bytes which may be pushed back can be specified
     26  * during construction. If the buffer of pushed back bytes is empty, bytes are
     27  * read from the underlying input stream.
     28  */
     29 public class PushbackInputStream extends FilterInputStream {
     30     /**
     31      * The buffer that contains pushed-back bytes.
     32      */
     33     protected byte[] buf;
     34 
     35     /**
     36      * The current position within {@code buf}. A value equal to
     37      * {@code buf.length} indicates that no bytes are available. A value of 0
     38      * indicates that the buffer is full.
     39      */
     40     protected int pos;
     41 
     42     /**
     43      * Constructs a new {@code PushbackInputStream} with the specified input
     44      * stream as source. The size of the pushback buffer is set to the default
     45      * value of 1 byte.
     46      *
     47      * <p><strong>Warning:</strong> passing a null source creates an invalid
     48      * {@code PushbackInputStream}. All read operations on such a stream will
     49      * fail.
     50      *
     51      * @param in
     52      *            the source input stream.
     53      */
     54     public PushbackInputStream(InputStream in) {
     55         super(in);
     56         buf = (in == null) ? null : new byte[1];
     57         pos = 1;
     58     }
     59 
     60     /**
     61      * Constructs a new {@code PushbackInputStream} with {@code in} as source
     62      * input stream. The size of the pushback buffer is set to {@code size}.
     63      *
     64      * <p><strong>Warning:</strong> passing a null source creates an invalid
     65      * {@code PushbackInputStream}. All read operations on such a stream will
     66      * fail.
     67      *
     68      * @param in
     69      *            the source input stream.
     70      * @param size
     71      *            the size of the pushback buffer.
     72      * @throws IllegalArgumentException
     73      *             if {@code size} is negative.
     74      */
     75     public PushbackInputStream(InputStream in, int size) {
     76         super(in);
     77         if (size <= 0) {
     78             throw new IllegalArgumentException("size <= 0");
     79         }
     80         buf = (in == null) ? null : new byte[size];
     81         pos = size;
     82     }
     83 
     84     @Override
     85     public int available() throws IOException {
     86         if (buf == null) {
     87             throw new IOException();
     88         }
     89         return buf.length - pos + in.available();
     90     }
     91 
     92     /**
     93      * Closes this stream. This implementation closes the source stream
     94      * and releases the pushback buffer.
     95      *
     96      * @throws IOException
     97      *             if an error occurs while closing this stream.
     98      */
     99     @Override
    100     public void close() throws IOException {
    101         if (in != null) {
    102             in.close();
    103             in = null;
    104             buf = null;
    105         }
    106     }
    107 
    108     /**
    109      * Indicates whether this stream supports the {@code mark(int)} and
    110      * {@code reset()} methods. {@code PushbackInputStream} does not support
    111      * them, so it returns {@code false}.
    112      *
    113      * @return always {@code false}.
    114      * @see #mark(int)
    115      * @see #reset()
    116      */
    117     @Override
    118     public boolean markSupported() {
    119         return false;
    120     }
    121 
    122     /**
    123      * Reads a single byte from this stream and returns it as an integer in the
    124      * range from 0 to 255. If the pushback buffer does not contain any
    125      * available bytes then a byte from the source input stream is returned.
    126      * Blocks until one byte has been read, the end of the source stream is
    127      * detected or an exception is thrown.
    128      *
    129      * @return the byte read or -1 if the end of the source stream has been
    130      *         reached.
    131      * @throws IOException
    132      *             if this stream is closed or an I/O error occurs while reading
    133      *             from this stream.
    134      */
    135     @Override
    136     public int read() throws IOException {
    137         if (buf == null) {
    138             throw new IOException();
    139         }
    140         // Is there a pushback byte available?
    141         if (pos < buf.length) {
    142             return (buf[pos++] & 0xFF);
    143         }
    144         // Assume read() in the InputStream will return low-order byte or -1
    145         // if end of stream.
    146         return in.read();
    147     }
    148 
    149     /**
    150      * Reads up to {@code byteCount} bytes from this stream and stores them in
    151      * the byte array {@code buffer} starting at {@code byteOffset}. Bytes are read
    152      * from the pushback buffer first, then from the source stream if more bytes
    153      * are required. Blocks until {@code byteCount} bytes have been read, the end of
    154      * the source stream is detected or an exception is thrown. Returns the number of bytes read,
    155      * or -1 if the end of the source stream has been reached.
    156      *
    157      * @throws IndexOutOfBoundsException
    158      *     if {@code byteOffset < 0 || byteCount < 0 || byteOffset + byteCount > buffer.length}.
    159      * @throws IOException
    160      *             if this stream is closed or another I/O error occurs while
    161      *             reading from this stream.
    162      * @throws NullPointerException
    163      *             if {@code buffer == null}.
    164      */
    165     @Override
    166     public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
    167         if (buf == null) {
    168             throw streamClosed();
    169         }
    170         Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
    171         int copiedBytes = 0, copyLength = 0, newOffset = byteOffset;
    172         // Are there pushback bytes available?
    173         if (pos < buf.length) {
    174             copyLength = (buf.length - pos >= byteCount) ? byteCount : buf.length - pos;
    175             System.arraycopy(buf, pos, buffer, newOffset, copyLength);
    176             newOffset += copyLength;
    177             copiedBytes += copyLength;
    178             // Use up the bytes in the local buffer
    179             pos += copyLength;
    180         }
    181         // Have we copied enough?
    182         if (copyLength == byteCount) {
    183             return byteCount;
    184         }
    185         int inCopied = in.read(buffer, newOffset, byteCount - copiedBytes);
    186         if (inCopied > 0) {
    187             return inCopied + copiedBytes;
    188         }
    189         if (copiedBytes == 0) {
    190             return inCopied;
    191         }
    192         return copiedBytes;
    193     }
    194 
    195     private IOException streamClosed() throws IOException  {
    196         throw new IOException("PushbackInputStream is closed");
    197     }
    198 
    199     /**
    200      * Skips {@code byteCount} bytes in this stream. This implementation skips bytes
    201      * in the pushback buffer first and then in the source stream if necessary.
    202      *
    203      * @return the number of bytes actually skipped.
    204      * @throws IOException
    205      *             if this stream is closed or another I/O error occurs.
    206      */
    207     @Override
    208     public long skip(long byteCount) throws IOException {
    209         if (in == null) {
    210             throw streamClosed();
    211         }
    212         if (byteCount <= 0) {
    213             return 0;
    214         }
    215         int numSkipped = 0;
    216         if (pos < buf.length) {
    217             numSkipped += (byteCount < buf.length - pos) ? byteCount : buf.length - pos;
    218             pos += numSkipped;
    219         }
    220         if (numSkipped < byteCount) {
    221             numSkipped += in.skip(byteCount - numSkipped);
    222         }
    223         return numSkipped;
    224     }
    225 
    226     /**
    227      * Pushes all the bytes in {@code buffer} back to this stream. The bytes are
    228      * pushed back in such a way that the next byte read from this stream is
    229      * buffer[0], then buffer[1] and so on.
    230      * <p>
    231      * If this stream's internal pushback buffer cannot store the entire
    232      * contents of {@code buffer}, an {@code IOException} is thrown. Parts of
    233      * {@code buffer} may have already been copied to the pushback buffer when
    234      * the exception is thrown.
    235      *
    236      * @param buffer
    237      *            the buffer containing the bytes to push back to this stream.
    238      * @throws IOException
    239      *             if the free space in the internal pushback buffer is not
    240      *             sufficient to store the contents of {@code buffer}.
    241      */
    242     public void unread(byte[] buffer) throws IOException {
    243         unread(buffer, 0, buffer.length);
    244     }
    245 
    246     /**
    247      * Pushes a subset of the bytes in {@code buffer} back to this stream. The
    248      * subset is defined by the start position {@code offset} within
    249      * {@code buffer} and the number of bytes specified by {@code length}. The
    250      * bytes are pushed back in such a way that the next byte read from this
    251      * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on.
    252      * <p>
    253      * If this stream's internal pushback buffer cannot store the selected
    254      * subset of {@code buffer}, an {@code IOException} is thrown. Parts of
    255      * {@code buffer} may have already been copied to the pushback buffer when
    256      * the exception is thrown.
    257      *
    258      * @param buffer
    259      *            the buffer containing the bytes to push back to this stream.
    260      * @param offset
    261      *            the index of the first byte in {@code buffer} to push back.
    262      * @param length
    263      *            the number of bytes to push back.
    264      * @throws IndexOutOfBoundsException
    265      *             if {@code offset < 0} or {@code length < 0}, or if
    266      *             {@code offset + length} is greater than the length of
    267      *             {@code buffer}.
    268      * @throws IOException
    269      *             if the free space in the internal pushback buffer is not
    270      *             sufficient to store the selected contents of {@code buffer}.
    271      */
    272     public void unread(byte[] buffer, int offset, int length) throws IOException {
    273         if (length > pos) {
    274             throw new IOException("Pushback buffer full");
    275         }
    276         Arrays.checkOffsetAndCount(buffer.length, offset, length);
    277         if (buf == null) {
    278             throw streamClosed();
    279         }
    280 
    281         System.arraycopy(buffer, offset, buf, pos - length, length);
    282         pos = pos - length;
    283     }
    284 
    285     /**
    286      * Pushes the specified byte {@code oneByte} back to this stream. Only the
    287      * least significant byte of the integer {@code oneByte} is pushed back.
    288      * This is done in such a way that the next byte read from this stream is
    289      * {@code (byte) oneByte}.
    290      * <p>
    291      * If this stream's internal pushback buffer cannot store the byte, an
    292      * {@code IOException} is thrown.
    293      *
    294      * @param oneByte
    295      *            the byte to push back to this stream.
    296      * @throws IOException
    297      *             if this stream is closed or the internal pushback buffer is
    298      *             full.
    299      */
    300     public void unread(int oneByte) throws IOException {
    301         if (buf == null) {
    302             throw new IOException();
    303         }
    304         if (pos == 0) {
    305             throw new IOException("Pushback buffer full");
    306         }
    307         buf[--pos] = (byte) oneByte;
    308     }
    309 
    310     /**
    311      * Marks the current position in this stream. Setting a mark is not
    312      * supported in this class; this implementation does nothing.
    313      *
    314      * @param readlimit
    315      *            the number of bytes that can be read from this stream before
    316      *            the mark is invalidated; this parameter is ignored.
    317      */
    318     @Override public void mark(int readlimit) {
    319     }
    320 
    321     /**
    322      * Resets this stream to the last marked position. Resetting the stream is
    323      * not supported in this class; this implementation always throws an
    324      * {@code IOException}.
    325      *
    326      * @throws IOException
    327      *             if this method is called.
    328      */
    329     @Override
    330     public void reset() throws IOException {
    331         throw new IOException();
    332     }
    333 }
    334