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