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 }