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 18package java.io; 19 20import java.util.Arrays; 21 22/** 23 * Wraps an existing {@link Reader} and adds functionality to "push back" 24 * characters that have been read, so that they can be read again. Parsers may 25 * find this useful. The number of characters which may be pushed back can be 26 * specified during construction. If the buffer of pushed back bytes is empty, 27 * characters are read from the underlying reader. 28 */ 29public class PushbackReader extends FilterReader { 30 /** 31 * The {@code char} array containing the chars to read. 32 */ 33 char[] buf; 34 35 /** 36 * The current position within the char array {@code buf}. A value 37 * equal to buf.length indicates no chars available. A value of 0 indicates 38 * the buffer is full. 39 */ 40 int pos; 41 42 /** 43 * Constructs a new {@code PushbackReader} with the specified reader as 44 * source. The size of the pushback buffer is set to the default value of 1 45 * character. 46 * 47 * @param in 48 * the source reader. 49 */ 50 public PushbackReader(Reader in) { 51 super(in); 52 buf = new char[1]; 53 pos = 1; 54 } 55 56 /** 57 * Constructs a new {@code PushbackReader} with {@code in} as source reader. 58 * The size of the pushback buffer is set to {@code size}. 59 * 60 * @param in 61 * the source reader. 62 * @param size 63 * the size of the pushback buffer. 64 * @throws IllegalArgumentException 65 * if {@code size} is negative. 66 */ 67 public PushbackReader(Reader in, int size) { 68 super(in); 69 if (size <= 0) { 70 throw new IllegalArgumentException("size <= 0"); 71 } 72 buf = new char[size]; 73 pos = size; 74 } 75 76 /** 77 * Closes this reader. This implementation closes the source reader 78 * and releases the pushback buffer. 79 * 80 * @throws IOException 81 * if an error occurs while closing this reader. 82 */ 83 @Override 84 public void close() throws IOException { 85 synchronized (lock) { 86 buf = null; 87 in.close(); 88 } 89 } 90 91 /** 92 * Marks the current position in this stream. Setting a mark is not 93 * supported in this class; this implementation always throws an 94 * {@code IOException}. 95 * 96 * @param readAheadLimit 97 * the number of character that can be read from this reader 98 * before the mark is invalidated; this parameter is ignored. 99 * @throws IOException 100 * if this method is called. 101 */ 102 @Override 103 public void mark(int readAheadLimit) throws IOException { 104 throw new IOException("mark/reset not supported"); 105 } 106 107 /** 108 * Indicates whether this reader supports the {@code mark(int)} and 109 * {@code reset()} methods. {@code PushbackReader} does not support them, so 110 * it returns {@code false}. 111 * 112 * @return always {@code false}. 113 * @see #mark(int) 114 * @see #reset() 115 */ 116 @Override 117 public boolean markSupported() { 118 return false; 119 } 120 121 /** 122 * Reads a single character from this reader and returns it as an integer 123 * with the two higher-order bytes set to 0. Returns -1 if the end of the 124 * reader has been reached. If the pushback buffer does not contain any 125 * available characters then a character from the source reader is returned. 126 * Blocks until one character has been read, the end of the source reader is 127 * detected or an exception is thrown. 128 * 129 * @return the character read or -1 if the end of the source reader has been 130 * reached. 131 * @throws IOException 132 * if this reader is closed or an I/O error occurs while reading 133 * from this reader. 134 */ 135 @Override 136 public int read() throws IOException { 137 synchronized (lock) { 138 checkNotClosed(); 139 /* Is there a pushback character available? */ 140 if (pos < buf.length) { 141 return buf[pos++]; 142 } 143 /** 144 * Assume read() in the InputStream will return 2 lowest-order bytes 145 * or -1 if end of stream. 146 */ 147 return in.read(); 148 } 149 } 150 151 private void checkNotClosed() throws IOException { 152 if (buf == null) { 153 throw new IOException("PushbackReader is closed"); 154 } 155 } 156 157 /** 158 * Reads at most {@code length} bytes from this reader and stores them in 159 * byte array {@code buffer} starting at {@code offset}. Characters are 160 * read from the pushback buffer first, then from the source reader if more 161 * bytes are required. Blocks until {@code count} characters have been read, 162 * the end of the source reader is detected or an exception is thrown. 163 * 164 * @param buffer 165 * the array in which to store the characters read from this 166 * reader. 167 * @param offset 168 * the initial position in {@code buffer} to store the characters 169 * read from this reader. 170 * @param count 171 * the maximum number of bytes to store in {@code buffer}. 172 * @return the number of bytes read or -1 if the end of the source reader 173 * has been reached. 174 * @throws IndexOutOfBoundsException 175 * if {@code offset < 0} or {@code count < 0}, or if 176 * {@code offset + count} is greater than the length of 177 * {@code buffer}. 178 * @throws IOException 179 * if this reader is closed or another I/O error occurs while 180 * reading from this reader. 181 */ 182 @Override 183 public int read(char[] buffer, int offset, int count) throws IOException { 184 synchronized (lock) { 185 checkNotClosed(); 186 Arrays.checkOffsetAndCount(buffer.length, offset, count); 187 188 int copiedChars = 0; 189 int copyLength = 0; 190 int newOffset = offset; 191 /* Are there pushback chars available? */ 192 if (pos < buf.length) { 193 copyLength = (buf.length - pos >= count) ? count : buf.length 194 - pos; 195 System.arraycopy(buf, pos, buffer, newOffset, copyLength); 196 newOffset += copyLength; 197 copiedChars += copyLength; 198 /* Use up the chars in the local buffer */ 199 pos += copyLength; 200 } 201 /* Have we copied enough? */ 202 if (copyLength == count) { 203 return count; 204 } 205 int inCopied = in.read(buffer, newOffset, count - copiedChars); 206 if (inCopied > 0) { 207 return inCopied + copiedChars; 208 } 209 if (copiedChars == 0) { 210 return inCopied; 211 } 212 return copiedChars; 213 } 214 } 215 216 /** 217 * Indicates whether this reader is ready to be read without blocking. 218 * Returns {@code true} if this reader will not block when {@code read} is 219 * called, {@code false} if unknown or blocking will occur. 220 * 221 * @return {@code true} if the receiver will not block when 222 * {@code read()} is called, {@code false} if unknown 223 * or blocking will occur. 224 * @throws IOException 225 * if this reader is closed or some other I/O error occurs. 226 * @see #read() 227 * @see #read(char[], int, int) 228 */ 229 @Override 230 public boolean ready() throws IOException { 231 synchronized (lock) { 232 if (buf == null) { 233 throw new IOException("Reader is closed"); 234 } 235 return (buf.length - pos > 0 || in.ready()); 236 } 237 } 238 239 /** 240 * Resets this reader to the last marked position. Resetting the reader is 241 * not supported in this class; this implementation always throws an 242 * {@code IOException}. 243 * 244 * @throws IOException 245 * if this method is called. 246 */ 247 @Override 248 public void reset() throws IOException { 249 throw new IOException("mark/reset not supported"); 250 } 251 252 /** 253 * Pushes all the characters in {@code buffer} back to this reader. The 254 * characters are pushed back in such a way that the next character read 255 * from this reader is buffer[0], then buffer[1] and so on. 256 * <p> 257 * If this reader's internal pushback buffer cannot store the entire 258 * contents of {@code buffer}, an {@code IOException} is thrown. Parts of 259 * {@code buffer} may have already been copied to the pushback buffer when 260 * the exception is thrown. 261 * 262 * @param buffer 263 * the buffer containing the characters to push back to this 264 * reader. 265 * @throws IOException 266 * if this reader is closed or the free space in the internal 267 * pushback buffer is not sufficient to store the contents of 268 * {@code buffer}. 269 */ 270 public void unread(char[] buffer) throws IOException { 271 unread(buffer, 0, buffer.length); 272 } 273 274 /** 275 * Pushes a subset of the characters in {@code buffer} back to this reader. 276 * The subset is defined by the start position {@code offset} within 277 * {@code buffer} and the number of characters specified by {@code length}. 278 * The bytes are pushed back in such a way that the next byte read from this 279 * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on. 280 * <p> 281 * If this stream's internal pushback buffer cannot store the selected 282 * subset of {@code buffer}, an {@code IOException} is thrown. Parts of 283 * {@code buffer} may have already been copied to the pushback buffer when 284 * the exception is thrown. 285 * 286 * @param buffer 287 * the buffer containing the characters to push back to this 288 * reader. 289 * @param offset 290 * the index of the first byte in {@code buffer} to push back. 291 * @param length 292 * the number of bytes to push back. 293 * @throws IndexOutOfBoundsException 294 * if {@code offset < 0} or {@code count < 0}, or if 295 * {@code offset + count} is greater than the length of 296 * {@code buffer}. 297 * @throws IOException 298 * if this reader is closed or the free space in the internal 299 * pushback buffer is not sufficient to store the selected 300 * contents of {@code buffer}. 301 * @throws NullPointerException 302 * if {@code buffer} is {@code null}. 303 */ 304 public void unread(char[] buffer, int offset, int length) throws IOException { 305 synchronized (lock) { 306 checkNotClosed(); 307 if (length > pos) { 308 throw new IOException("Pushback buffer full"); 309 } 310 Arrays.checkOffsetAndCount(buffer.length, offset, length); 311 for (int i = offset + length - 1; i >= offset; i--) { 312 unread(buffer[i]); 313 } 314 } 315 } 316 317 /** 318 * Pushes the specified character {@code oneChar} back to this reader. This 319 * is done in such a way that the next character read from this reader is 320 * {@code (char) oneChar}. 321 * <p> 322 * If this reader's internal pushback buffer cannot store the character, an 323 * {@code IOException} is thrown. 324 * 325 * @param oneChar 326 * the character to push back to this stream. 327 * @throws IOException 328 * if this reader is closed or the internal pushback buffer is 329 * full. 330 */ 331 public void unread(int oneChar) throws IOException { 332 synchronized (lock) { 333 checkNotClosed(); 334 if (pos == 0) { 335 throw new IOException("Pushback buffer full"); 336 } 337 buf[--pos] = (char) oneChar; 338 } 339 } 340 341 /** 342 * Skips {@code charCount} characters in this reader. This implementation skips 343 * characters in the pushback buffer first and then in the source reader if 344 * necessary. 345 * 346 * @return the number of characters actually skipped. 347 * @throws IllegalArgumentException if {@code charCount < 0}. 348 * @throws IOException 349 * if this reader is closed or another I/O error occurs. 350 */ 351 @Override 352 public long skip(long charCount) throws IOException { 353 if (charCount < 0) { 354 throw new IllegalArgumentException("charCount < 0: " + charCount); 355 } 356 synchronized (lock) { 357 checkNotClosed(); 358 if (charCount == 0) { 359 return 0; 360 } 361 long inSkipped; 362 int availableFromBuffer = buf.length - pos; 363 if (availableFromBuffer > 0) { 364 long requiredFromIn = charCount - availableFromBuffer; 365 if (requiredFromIn <= 0) { 366 pos += charCount; 367 return charCount; 368 } 369 pos += availableFromBuffer; 370 inSkipped = in.skip(requiredFromIn); 371 } else { 372 inSkipped = in.skip(charCount); 373 } 374 return inSkipped + availableFromBuffer; 375 } 376 } 377} 378