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