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 up to {@code count} characters from this reader and stores them in 159 * character 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 * Returns the number of bytes read or -1 if the end of the source reader has been reached. 164 * 165 * @throws IndexOutOfBoundsException 166 * if {@code offset < 0 || count < 0 || offset + count > buffer.length}. 167 * @throws IOException 168 * if this reader is closed or another I/O error occurs while 169 * reading from this reader. 170 */ 171 @Override 172 public int read(char[] buffer, int offset, int count) throws IOException { 173 synchronized (lock) { 174 checkNotClosed(); 175 Arrays.checkOffsetAndCount(buffer.length, offset, count); 176 177 int copiedChars = 0; 178 int copyLength = 0; 179 int newOffset = offset; 180 /* Are there pushback chars available? */ 181 if (pos < buf.length) { 182 copyLength = (buf.length - pos >= count) ? count : buf.length 183 - pos; 184 System.arraycopy(buf, pos, buffer, newOffset, copyLength); 185 newOffset += copyLength; 186 copiedChars += copyLength; 187 /* Use up the chars in the local buffer */ 188 pos += copyLength; 189 } 190 /* Have we copied enough? */ 191 if (copyLength == count) { 192 return count; 193 } 194 int inCopied = in.read(buffer, newOffset, count - copiedChars); 195 if (inCopied > 0) { 196 return inCopied + copiedChars; 197 } 198 if (copiedChars == 0) { 199 return inCopied; 200 } 201 return copiedChars; 202 } 203 } 204 205 /** 206 * Indicates whether this reader is ready to be read without blocking. 207 * Returns {@code true} if this reader will not block when {@code read} is 208 * called, {@code false} if unknown or blocking will occur. 209 * 210 * @return {@code true} if the receiver will not block when 211 * {@code read()} is called, {@code false} if unknown 212 * or blocking will occur. 213 * @throws IOException 214 * if this reader is closed or some other I/O error occurs. 215 * @see #read() 216 * @see #read(char[], int, int) 217 */ 218 @Override 219 public boolean ready() throws IOException { 220 synchronized (lock) { 221 if (buf == null) { 222 throw new IOException("Reader is closed"); 223 } 224 return (buf.length - pos > 0 || in.ready()); 225 } 226 } 227 228 /** 229 * Resets this reader to the last marked position. Resetting the reader is 230 * not supported in this class; this implementation always throws an 231 * {@code IOException}. 232 * 233 * @throws IOException 234 * if this method is called. 235 */ 236 @Override 237 public void reset() throws IOException { 238 throw new IOException("mark/reset not supported"); 239 } 240 241 /** 242 * Pushes all the characters in {@code buffer} back to this reader. The 243 * characters are pushed back in such a way that the next character read 244 * from this reader is buffer[0], then buffer[1] and so on. 245 * <p> 246 * If this reader's internal pushback buffer cannot store the entire 247 * contents of {@code buffer}, an {@code IOException} is thrown. Parts of 248 * {@code buffer} may have already been copied to the pushback buffer when 249 * the exception is thrown. 250 * 251 * @param buffer 252 * the buffer containing the characters to push back to this 253 * reader. 254 * @throws IOException 255 * if this reader is closed or the free space in the internal 256 * pushback buffer is not sufficient to store the contents of 257 * {@code buffer}. 258 */ 259 public void unread(char[] buffer) throws IOException { 260 unread(buffer, 0, buffer.length); 261 } 262 263 /** 264 * Pushes a subset of the characters in {@code buffer} back to this reader. 265 * The subset is defined by the start position {@code offset} within 266 * {@code buffer} and the number of characters specified by {@code length}. 267 * The bytes are pushed back in such a way that the next byte read from this 268 * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on. 269 * <p> 270 * If this stream's internal pushback buffer cannot store the selected 271 * subset of {@code buffer}, an {@code IOException} is thrown. Parts of 272 * {@code buffer} may have already been copied to the pushback buffer when 273 * the exception is thrown. 274 * 275 * @param buffer 276 * the buffer containing the characters to push back to this 277 * reader. 278 * @param offset 279 * the index of the first byte in {@code buffer} to push back. 280 * @param length 281 * the number of bytes to push back. 282 * @throws IndexOutOfBoundsException 283 * if {@code offset < 0} or {@code count < 0}, or if 284 * {@code offset + count} is greater than the length of 285 * {@code buffer}. 286 * @throws IOException 287 * if this reader is closed or the free space in the internal 288 * pushback buffer is not sufficient to store the selected 289 * contents of {@code buffer}. 290 * @throws NullPointerException 291 * if {@code buffer} is {@code null}. 292 */ 293 public void unread(char[] buffer, int offset, int length) throws IOException { 294 synchronized (lock) { 295 checkNotClosed(); 296 if (length > pos) { 297 throw new IOException("Pushback buffer full"); 298 } 299 Arrays.checkOffsetAndCount(buffer.length, offset, length); 300 for (int i = offset + length - 1; i >= offset; i--) { 301 unread(buffer[i]); 302 } 303 } 304 } 305 306 /** 307 * Pushes the specified character {@code oneChar} back to this reader. This 308 * is done in such a way that the next character read from this reader is 309 * {@code (char) oneChar}. 310 * <p> 311 * If this reader's internal pushback buffer cannot store the character, an 312 * {@code IOException} is thrown. 313 * 314 * @param oneChar 315 * the character to push back to this stream. 316 * @throws IOException 317 * if this reader is closed or the internal pushback buffer is 318 * full. 319 */ 320 public void unread(int oneChar) throws IOException { 321 synchronized (lock) { 322 checkNotClosed(); 323 if (pos == 0) { 324 throw new IOException("Pushback buffer full"); 325 } 326 buf[--pos] = (char) oneChar; 327 } 328 } 329 330 /** 331 * Skips {@code charCount} characters in this reader. This implementation skips 332 * characters in the pushback buffer first and then in the source reader if 333 * necessary. 334 * 335 * @return the number of characters actually skipped. 336 * @throws IllegalArgumentException if {@code charCount < 0}. 337 * @throws IOException 338 * if this reader is closed or another I/O error occurs. 339 */ 340 @Override 341 public long skip(long charCount) throws IOException { 342 if (charCount < 0) { 343 throw new IllegalArgumentException("charCount < 0: " + charCount); 344 } 345 synchronized (lock) { 346 checkNotClosed(); 347 if (charCount == 0) { 348 return 0; 349 } 350 long inSkipped; 351 int availableFromBuffer = buf.length - pos; 352 if (availableFromBuffer > 0) { 353 long requiredFromIn = charCount - availableFromBuffer; 354 if (requiredFromIn <= 0) { 355 pos += charCount; 356 return charCount; 357 } 358 pos += availableFromBuffer; 359 inSkipped = in.skip(requiredFromIn); 360 } else { 361 inSkipped = in.skip(charCount); 362 } 363 return inSkipped + availableFromBuffer; 364 } 365 } 366} 367