1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.io.PushbackInputStream; 13 14 15 import hunt.io.FilterInputStream; 16 import hunt.io.Common; 17 import hunt.Exceptions; 18 import hunt.Integer; 19 import hunt.Long; 20 import hunt.math.Helper; 21 22 /** 23 */ 24 class PushbackInputStream : FilterInputStream { 25 /** 26 * The pushback buffer. 27 * @since 1.1 28 */ 29 protected byte[] buf; 30 31 /** 32 * The position within the pushback buffer from which the next byte will 33 * be read. When the buffer is empty, <code>pos</code> is equal to 34 * <code>buf.length</code>; when the buffer is full, <code>pos</code> is 35 * equal to zero. 36 * 37 * @since 1.1 38 */ 39 protected int pos; 40 41 /** 42 * Check to make sure that this stream has not been closed 43 */ 44 private void ensureOpen() { 45 if (inputStream is null) 46 throw new IOException("Stream closed"); 47 } 48 49 /** 50 * Creates a <code>PushbackInputStream</code> 51 * with a pushback buffer of the specified <code>size</code>, 52 * and saves its argument, the input stream 53 * <code>inputStream</code>, for later use. Initially, 54 * the pushback buffer is empty. 55 * 56 * @param inputStream the input stream from which bytes will be read. 57 * @param size the size of the pushback buffer. 58 * @exception IllegalArgumentException if {@code size <= 0} 59 * @since 1.1 60 */ 61 public this(InputStream inputStream, int size) { 62 super(inputStream); 63 if (size <= 0) { 64 throw new IllegalArgumentException("size <= 0"); 65 } 66 this.buf = new byte[size]; 67 this.pos = size; 68 } 69 70 /** 71 * Creates a <code>PushbackInputStream</code> 72 * with a 1-byte pushback buffer, and saves its argument, the input stream 73 * <code>inputStream</code>, for later use. Initially, 74 * the pushback buffer is empty. 75 * 76 * @param inputStream the input stream from which bytes will be read. 77 */ 78 public this(InputStream inputStream) { 79 this(inputStream, 1); 80 } 81 82 /** 83 * Reads the next byte of data from this input stream. The value 84 * byte is returned as an <code>int</code> inputStream the range 85 * <code>0</code> to <code>255</code>. If no byte is available 86 * because the end of the stream has been reached, the value 87 * <code>-1</code> is returned. This method blocks until input data 88 * is available, the end of the stream is detected, or an exception 89 * is thrown. 90 * 91 * <p> This method returns the most recently pushed-back byte, if there is 92 * one, and otherwise calls the <code>read</code> method of its underlying 93 * input stream and returns whatever value that method returns. 94 * 95 * @return the next byte of data, or <code>-1</code> if the end of the 96 * stream has been reached. 97 * @exception IOException if this input stream has been closed by 98 * invoking its {@link #close()} method, 99 * or an I/O error occurs. 100 * @see java.io.InputStream#read() 101 */ 102 override 103 public int read() { 104 ensureOpen(); 105 if (pos < buf.length) { 106 return buf[pos++] & 0xff; 107 } 108 return super.read(); 109 } 110 111 /** 112 * Reads up to <code>len</code> bytes of data from this input stream into 113 * an array of bytes. This method first reads any pushed-back bytes; after 114 * that, if fewer than <code>len</code> bytes have been read then it 115 * reads from the underlying input stream. If <code>len</code> is not zero, the method 116 * blocks until at least 1 byte of input is available; otherwise, no 117 * bytes are read and <code>0</code> is returned. 118 * 119 * @param b the buffer into which the data is read. 120 * @param off the start offset inputStream the destination array <code>b</code> 121 * @param len the maximum number of bytes read. 122 * @return the total number of bytes read into the buffer, or 123 * <code>-1</code> if there is no more data because the end of 124 * the stream has been reached. 125 * @exception NullPointerException If <code>b</code> is <code>null</code>. 126 * @exception IndexOutOfBoundsException If <code>off</code> is negative, 127 * <code>len</code> is negative, or <code>len</code> is greater than 128 * <code>b.length - off</code> 129 * @exception IOException if this input stream has been closed by 130 * invoking its {@link #close()} method, 131 * or an I/O error occurs. 132 * @see java.io.InputStream#read(byte[], int, int) 133 */ 134 override 135 public int read(byte[] b, int off, int len) { 136 ensureOpen(); 137 if (b is null) { 138 throw new NullPointerException(); 139 } else if (off < 0 || len < 0 || len > b.length - off) { 140 throw new IndexOutOfBoundsException(); 141 } else if (len == 0) { 142 return 0; 143 } 144 145 int avail = cast(int)(buf.length - pos); 146 if (avail > 0) { 147 if (len < avail) { 148 avail = len; 149 } 150 // System.arraycopy(buf, pos, b, off, avail); 151 b[off..off+avail] = buf[pos..pos + avail]; 152 pos += avail; 153 off += avail; 154 len -= avail; 155 } 156 if (len > 0) { 157 len = super.read(b, off, len); 158 if (len == -1) { 159 return avail == 0 ? -1 : avail; 160 } 161 return avail + len; 162 } 163 return avail; 164 } 165 166 /** 167 * Pushes back a byte by copying it to the front of the pushback buffer. 168 * After this method returns, the next byte to be read will have the value 169 * <code>(byte)b</code>. 170 * 171 * @param b the <code>int</code> value whose low-order 172 * byte is to be pushed back. 173 * @exception IOException If there is not enough room inputStream the pushback 174 * buffer for the byte, or this input stream has been closed by 175 * invoking its {@link #close()} method. 176 */ 177 public void unread(int b) { 178 ensureOpen(); 179 if (pos == 0) { 180 throw new IOException("Push back buffer is full"); 181 } 182 buf[--pos] = cast(byte)b; 183 } 184 185 /** 186 * Pushes back a portion of an array of bytes by copying it to the front 187 * of the pushback buffer. After this method returns, the next byte to be 188 * read will have the value <code>b[off]</code>, the byte after that will 189 * have the value <code>b[off+1]</code>, and so forth. 190 * 191 * @param b the byte array to push back. 192 * @param off the start offset of the data. 193 * @param len the number of bytes to push back. 194 * @exception IOException If there is not enough room inputStream the pushback 195 * buffer for the specified number of bytes, 196 * or this input stream has been closed by 197 * invoking its {@link #close()} method. 198 * @since 1.1 199 */ 200 public void unread(byte[] b, int off, int len) { 201 ensureOpen(); 202 if (len > pos) { 203 throw new IOException("Push back buffer is full"); 204 } 205 pos -= len; 206 // System.arraycopy(b, off, buf, pos, len); 207 buf[pos .. pos + len] = b[off.. off+len]; 208 } 209 210 /** 211 * Pushes back an array of bytes by copying it to the front of the 212 * pushback buffer. After this method returns, the next byte to be read 213 * will have the value <code>b[0]</code>, the byte after that will have the 214 * value <code>b[1]</code>, and so forth. 215 * 216 * @param b the byte array to push back 217 * @exception IOException If there is not enough room inputStream the pushback 218 * buffer for the specified number of bytes, 219 * or this input stream has been closed by 220 * invoking its {@link #close()} method. 221 * @since 1.1 222 */ 223 public void unread(byte[] b) { 224 unread(b, 0, cast(int)(b.length)); 225 } 226 227 /** 228 * Returns an estimate of the number of bytes that can be read (or 229 * skipped over) from this input stream without blocking by the next 230 * invocation of a method for this input stream. The next invocation might be 231 * the same thread or another thread. A single read or skip of this 232 * many bytes will not block, but may read or skip fewer bytes. 233 * 234 * <p> The method returns the sum of the number of bytes that have been 235 * pushed back and the value returned by {@link 236 * java.io.FilterInputStream#available available}. 237 * 238 * @return the number of bytes that can be read (or skipped over) from 239 * the input stream without blocking. 240 * @exception IOException if this input stream has been closed by 241 * invoking its {@link #close()} method, 242 * or an I/O error occurs. 243 * @see java.io.FilterInputStream#inputStream 244 * @see java.io.InputStream#available() 245 */ 246 override 247 public int available() { 248 ensureOpen(); 249 int n = cast(int)(buf.length - pos); 250 int avail = super.available(); 251 return n > (Integer.MAX_VALUE - avail) 252 ? Integer.MAX_VALUE 253 : n + avail; 254 } 255 256 /** 257 * Skips over and discards <code>n</code> bytes of data from this 258 * input stream. The <code>skip</code> method may, for a variety of 259 * reasons, end up skipping over some smaller number of bytes, 260 * possibly zero. If <code>n</code> is negative, no bytes are skipped. 261 * 262 * <p> The <code>skip</code> method of <code>PushbackInputStream</code> 263 * first skips over the bytes inputStream the pushback buffer, if any. It then 264 * calls the <code>skip</code> method of the underlying input stream if 265 * more bytes need to be skipped. The actual number of bytes skipped 266 * is returned. 267 * 268 * @param n {@inheritDoc} 269 * @return {@inheritDoc} 270 * @throws IOException if the stream has been closed by 271 * invoking its {@link #close()} method, 272 * {@code inputStream.skip(n)} throws an IOException, 273 * or an I/O error occurs. 274 * @see java.io.FilterInputStream#inputStream 275 * @see java.io.InputStream#skip(long n) 276 * @since 1.2 277 */ 278 override 279 public long skip(long n) { 280 ensureOpen(); 281 if (n <= 0) { 282 return 0; 283 } 284 285 long pskip = buf.length - pos; 286 if (pskip > 0) { 287 if (n < pskip) { 288 pskip = n; 289 } 290 pos += pskip; 291 n -= pskip; 292 } 293 if (n > 0) { 294 pskip += super.skip(n); 295 } 296 return pskip; 297 } 298 299 /** 300 * Tests if this input stream supports the <code>mark</code> and 301 * <code>reset</code> methods, which it does not. 302 * 303 * @return <code>false</code>, since this class does not support the 304 * <code>mark</code> and <code>reset</code> methods. 305 * @see java.io.InputStream#mark(int) 306 * @see java.io.InputStream#reset() 307 */ 308 override 309 public bool markSupported() { 310 return false; 311 } 312 313 /** 314 * Marks the current position inputStream this input stream. 315 * 316 * <p> The <code>mark</code> method of <code>PushbackInputStream</code> 317 * does nothing. 318 * 319 * @param readlimit the maximum limit of bytes that can be read before 320 * the mark position becomes invalid. 321 * @see java.io.InputStream#reset() 322 */ 323 public synchronized void mark(int readlimit) { 324 } 325 326 /** 327 * Repositions this stream to the position at the time the 328 * <code>mark</code> method was last called on this input stream. 329 * 330 * <p> The method <code>reset</code> for class 331 * <code>PushbackInputStream</code> does nothing except throw an 332 * <code>IOException</code>. 333 * 334 * @exception IOException if this method is invoked. 335 * @see java.io.InputStream#mark(int) 336 * @see java.io.IOException 337 */ 338 public synchronized void reset() { 339 throw new IOException("mark/reset not supported"); 340 } 341 342 /** 343 * Closes this input stream and releases any system resources 344 * associated with the stream. 345 * Once the stream has been closed, further read(), unread(), 346 * available(), reset(), or skip() invocations will throw an IOException. 347 * Closing a previously closed stream has no effect. 348 * 349 * @exception IOException if an I/O error occurs. 350 */ 351 override 352 public void close() { 353 synchronized{ 354 if (inputStream is null) 355 return; 356 inputStream.close(); 357 inputStream = null; 358 buf = null; 359 } 360 361 } 362 }