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 }