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 */ PushbackInputStream(InputStream in)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 */ PushbackInputStream(InputStream in, int size)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 available()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 close()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 markSupported()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 read()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 at most {@code length} bytes from this stream and stores them in 151 * the byte array {@code buffer} starting at {@code offset}. Bytes are read 152 * from the pushback buffer first, then from the source stream if more bytes 153 * are required. Blocks until {@code count} bytes have been read, the end of 154 * the source stream is detected or an exception is thrown. 155 * 156 * @param buffer 157 * the array in which to store the bytes read from this stream. 158 * @param offset 159 * the initial position in {@code buffer} to store the bytes read 160 * from this stream. 161 * @param length 162 * the maximum number of bytes to store in {@code buffer}. 163 * @return the number of bytes read or -1 if the end of the source stream 164 * has been reached. 165 * @throws IndexOutOfBoundsException 166 * if {@code offset < 0} or {@code length < 0}, or if 167 * {@code offset + length} is greater than the length of 168 * {@code buffer}. 169 * @throws IOException 170 * if this stream is closed or another I/O error occurs while 171 * reading from this stream. 172 * @throws NullPointerException 173 * if {@code buffer} is {@code null}. 174 */ 175 @Override read(byte[] buffer, int offset, int length)176 public int read(byte[] buffer, int offset, int length) throws IOException { 177 if (buf == null) { 178 throw streamClosed(); 179 } 180 Arrays.checkOffsetAndCount(buffer.length, offset, length); 181 int copiedBytes = 0, copyLength = 0, newOffset = offset; 182 // Are there pushback bytes available? 183 if (pos < buf.length) { 184 copyLength = (buf.length - pos >= length) ? length : buf.length 185 - pos; 186 System.arraycopy(buf, pos, buffer, newOffset, copyLength); 187 newOffset += copyLength; 188 copiedBytes += copyLength; 189 // Use up the bytes in the local buffer 190 pos += copyLength; 191 } 192 // Have we copied enough? 193 if (copyLength == length) { 194 return length; 195 } 196 int inCopied = in.read(buffer, newOffset, length - copiedBytes); 197 if (inCopied > 0) { 198 return inCopied + copiedBytes; 199 } 200 if (copiedBytes == 0) { 201 return inCopied; 202 } 203 return copiedBytes; 204 } 205 streamClosed()206 private IOException streamClosed() throws IOException { 207 throw new IOException("PushbackInputStream is closed"); 208 } 209 210 /** 211 * Skips {@code byteCount} bytes in this stream. This implementation skips bytes 212 * in the pushback buffer first and then in the source stream if necessary. 213 * 214 * @return the number of bytes actually skipped. 215 * @throws IOException 216 * if this stream is closed or another I/O error occurs. 217 */ 218 @Override skip(long byteCount)219 public long skip(long byteCount) throws IOException { 220 if (in == null) { 221 throw streamClosed(); 222 } 223 if (byteCount <= 0) { 224 return 0; 225 } 226 int numSkipped = 0; 227 if (pos < buf.length) { 228 numSkipped += (byteCount < buf.length - pos) ? byteCount : buf.length - pos; 229 pos += numSkipped; 230 } 231 if (numSkipped < byteCount) { 232 numSkipped += in.skip(byteCount - numSkipped); 233 } 234 return numSkipped; 235 } 236 237 /** 238 * Pushes all the bytes in {@code buffer} back to this stream. The bytes are 239 * pushed back in such a way that the next byte read from this stream is 240 * buffer[0], then buffer[1] and so on. 241 * <p> 242 * If this stream's internal pushback buffer cannot store the entire 243 * contents of {@code buffer}, an {@code IOException} is thrown. Parts of 244 * {@code buffer} may have already been copied to the pushback buffer when 245 * the exception is thrown. 246 * 247 * @param buffer 248 * the buffer containing the bytes to push back to this stream. 249 * @throws IOException 250 * if the free space in the internal pushback buffer is not 251 * sufficient to store the contents of {@code buffer}. 252 */ unread(byte[] buffer)253 public void unread(byte[] buffer) throws IOException { 254 unread(buffer, 0, buffer.length); 255 } 256 257 /** 258 * Pushes a subset of the bytes in {@code buffer} back to this stream. The 259 * subset is defined by the start position {@code offset} within 260 * {@code buffer} and the number of bytes specified by {@code length}. The 261 * bytes are pushed back in such a way that the next byte read from this 262 * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on. 263 * <p> 264 * If this stream's internal pushback buffer cannot store the selected 265 * subset of {@code buffer}, an {@code IOException} is thrown. Parts of 266 * {@code buffer} may have already been copied to the pushback buffer when 267 * the exception is thrown. 268 * 269 * @param buffer 270 * the buffer containing the bytes to push back to this stream. 271 * @param offset 272 * the index of the first byte in {@code buffer} to push back. 273 * @param length 274 * the number of bytes to push back. 275 * @throws IndexOutOfBoundsException 276 * if {@code offset < 0} or {@code length < 0}, or if 277 * {@code offset + length} is greater than the length of 278 * {@code buffer}. 279 * @throws IOException 280 * if the free space in the internal pushback buffer is not 281 * sufficient to store the selected contents of {@code buffer}. 282 */ unread(byte[] buffer, int offset, int length)283 public void unread(byte[] buffer, int offset, int length) throws IOException { 284 if (length > pos) { 285 throw new IOException("Pushback buffer full"); 286 } 287 Arrays.checkOffsetAndCount(buffer.length, offset, length); 288 if (buf == null) { 289 throw streamClosed(); 290 } 291 292 System.arraycopy(buffer, offset, buf, pos - length, length); 293 pos = pos - length; 294 } 295 296 /** 297 * Pushes the specified byte {@code oneByte} back to this stream. Only the 298 * least significant byte of the integer {@code oneByte} is pushed back. 299 * This is done in such a way that the next byte read from this stream is 300 * {@code (byte) oneByte}. 301 * <p> 302 * If this stream's internal pushback buffer cannot store the byte, an 303 * {@code IOException} is thrown. 304 * 305 * @param oneByte 306 * the byte to push back to this stream. 307 * @throws IOException 308 * if this stream is closed or the internal pushback buffer is 309 * full. 310 */ unread(int oneByte)311 public void unread(int oneByte) throws IOException { 312 if (buf == null) { 313 throw new IOException(); 314 } 315 if (pos == 0) { 316 throw new IOException("Pushback buffer full"); 317 } 318 buf[--pos] = (byte) oneByte; 319 } 320 321 /** 322 * Marks the current position in this stream. Setting a mark is not 323 * supported in this class; this implementation does nothing. 324 * 325 * @param readlimit 326 * the number of bytes that can be read from this stream before 327 * the mark is invalidated; this parameter is ignored. 328 */ mark(int readlimit)329 @Override public void mark(int readlimit) { 330 } 331 332 /** 333 * Resets this stream to the last marked position. Resetting the stream is 334 * not supported in this class; this implementation always throws an 335 * {@code IOException}. 336 * 337 * @throws IOException 338 * if this method is called. 339 */ 340 @Override reset()341 public void reset() throws IOException { 342 throw new IOException(); 343 } 344 } 345