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.Buffer;
13 
14 import hunt.Exceptions;
15 
16 import std.conv;
17 import std.format;
18 
19 abstract class Buffer
20 {
21     // Invariants: mark <= position <= limit <= capacity
22     private int _mark = -1;
23     private int _position = 0;
24     private int _limit;
25     private int _capacity;
26 
27     // Creates a new buffer with the given mark, position, limit, and capacity,
28     // after checking invariants.
29     //
30     this(int mark, int pos, int lim, int cap) {       // package-private
31         if (cap < 0)
32             throw new IllegalArgumentException("Negative capacity: " ~ to!string(cap));
33         this._capacity = cap;
34         limit(lim);
35         position(pos);
36         if (mark >= 0) {
37             if (mark > pos)
38                 throw new IllegalArgumentException("mark > position: ("
39                                                    ~ to!string( mark) ~ " > " ~ to!string(pos) ~ ")");
40             this._mark = mark;
41         }
42     }
43 
44     /**
45      * Returns this buffer's capacity.
46      *
47      * @return  The capacity of this buffer
48      */
49     final int capacity() {
50         return _capacity;
51     }
52 
53     /**
54      * Returns this buffer's position.
55      *
56      * @return  The position of this buffer
57      */
58     final int position() {
59         return _position;
60     }
61 
62     /**
63      * Sets this buffer's position.  If the mark is defined and larger than the
64      * new position then it is discarded.
65      *
66      * @param  newPosition
67      *         The new position value; must be non-negative
68      *         and no larger than the current limit
69      *
70      * @return  This buffer
71      *
72      * @throws  IllegalArgumentException
73      *          If the preconditions on <tt>newPosition</tt> do not hold
74      */
75     final Buffer position(int newPosition) {
76         if ((newPosition > _limit) || (newPosition < 0))
77             throw new IllegalArgumentException(
78                     format("Out of range: %d is included in (0, %d)", newPosition, _limit));
79         _position = newPosition;
80         if (_mark > _position) _mark = -1;
81         return this;
82     }
83 
84     /**
85      * Returns this buffer's limit.
86      *
87      * @return  The limit of this buffer
88      */
89     final int limit() {
90         return _limit;
91     }
92 
93     /**
94      * Sets this buffer's limit.  If the position is larger than the new limit
95      * then it is set to the new limit.  If the mark is defined and larger than
96      * the new limit then it is discarded.
97      *
98      * @param  newLimit
99      *         The new limit value; must be non-negative
100      *         and no larger than this buffer's capacity
101      *
102      * @return  This buffer
103      *
104      * @throws  IllegalArgumentException
105      *          If the preconditions on <tt>newLimit</tt> do not hold
106      */
107     final Buffer limit(int newLimit) {
108         if ((newLimit > _capacity) || (newLimit < 0))
109             throw new IllegalArgumentException("");
110         _limit = newLimit;
111         if (_position > _limit) _position = _limit;
112         if (_mark > _limit) _mark = -1;
113         return this;
114     }
115 
116     /**
117      * Sets this buffer's mark at its position.
118      *
119      * @return  This buffer
120      */
121     final Buffer mark() {
122         _mark = _position;
123         return this;
124     }
125 
126     /**
127      * Resets this buffer's position to the previously-marked position.
128      *
129      * <p> Invoking this method neither changes nor discards the mark's
130      * value. </p>
131      *
132      * @return  This buffer
133      *
134      * @throws  InvalidMarkException
135      *          If the mark has not been set
136      */
137     final Buffer reset() {
138         int m = _mark;
139         if (m < 0)
140             throw new InvalidMarkException("");
141         _position = m;
142         return this;
143     }
144 
145     /**
146      * Clears this buffer.  The position is set to zero, the limit is set to
147      * the capacity, and the mark is discarded.
148      *
149      * <p> Invoke this method before using a sequence of channel-read or
150      * <i>put</i> operations to fill this buffer.  For example:
151      *
152      * <blockquote><pre>
153      * buf.clear();     // Prepare buffer for reading
154      * in.read(buf);    // Read data</pre></blockquote>
155      *
156      * <p> This method does not actually erase the data in the buffer, but it
157      * is named as if it did because it will most often be used in situations
158      * in which that might as well be the case. </p>
159      *
160      * @return  This buffer
161      */
162     final Buffer clear() {
163         _position = 0;
164         _limit = _capacity;
165         _mark = -1;
166         return this;
167     }
168 
169     /**
170      * Flips this buffer.  The limit is set to the current position and then
171      * the position is set to zero.  If the mark is defined then it is
172      * discarded.
173      *
174      * <p> After a sequence of channel-read or <i>put</i> operations, invoke
175      * this method to prepare for a sequence of channel-write or relative
176      * <i>get</i> operations.  For example:
177      *
178      * <blockquote><pre>
179      * buf.put(magic);    // Prepend header
180      * in.read(buf);      // Read data into rest of buffer
181      * buf.flip();        // Flip buffer
182      * out.write(buf);    // Write header + data to channel</pre></blockquote>
183      *
184      * <p> This method is often used in conjunction with the compact method when 
185      * transferring data from one place to another.  </p>
186      *
187      * @return  This buffer
188      */
189     final Buffer flip() {
190         _limit = _position;
191         _position = 0;
192         _mark = -1;
193         return this;
194     }
195 
196     /**
197      * Rewinds this buffer.  The position is set to zero and the mark is
198      * discarded.
199      *
200      * <p> Invoke this method before a sequence of channel-write or <i>get</i>
201      * operations, assuming that the limit has already been set
202      * appropriately.  For example:
203      *
204      * <blockquote><pre>
205      * out.write(buf);    // Write remaining data
206      * buf.rewind();      // Rewind buffer
207      * buf.get(array);    // Copy data into array</pre></blockquote>
208      *
209      * @return  This buffer
210      */
211     final Buffer rewind() {
212         _position = 0;
213         _mark = -1;
214         return this;
215     }
216 
217     /**
218      * Returns the number of elements between the current position and the
219      * limit.
220      *
221      * @return  The number of elements remaining in this buffer
222      */
223     final int remaining() {
224         return _limit - _position;
225     }
226 
227     /**
228      * Tells whether there are any elements between the current position and
229      * the limit.
230      *
231      * @return  <tt>true</tt> if, and only if, there is at least one element
232      *          remaining in this buffer
233      */
234     final bool hasRemaining() {
235         return _position < _limit;
236     }
237 
238     /**
239      * Tells whether or not this buffer is read-only.
240      *
241      * @return  <tt>true</tt> if, and only if, this buffer is read-only
242      */
243     abstract bool isReadOnly();
244 
245     /**
246      * Tells whether or not this buffer is backed by an accessible
247      * array.
248      *
249      * <p> If this method returns <tt>true</tt> then the {@link #array() array}
250      * and {@link #arrayOffset() arrayOffset} methods may safely be invoked.
251      * </p>
252      *
253      * @return  <tt>true</tt> if, and only if, this buffer
254      *          is backed by an array and is not read-only
255      *
256      */
257     abstract bool hasArray();
258 
259     /**
260      * Returns the array that backs this
261      * buffer&nbsp;&nbsp;<i>(optional operation)</i>.
262      *
263      * <p> This method is intended to allow array-backed buffers to be
264      * passed to native code more efficiently. Concrete subclasses
265      * provide more strongly-typed return values for this method.
266      *
267      * <p> Modifications to this buffer's content will cause the returned
268      * array's content to be modified, and vice versa.
269      *
270      * <p> Invoke the {@link #hasArray hasArray} method before invoking this
271      * method in order to ensure that this buffer has an accessible backing
272      * array.  </p>
273      *
274      * @return  The array that backs this buffer
275      *
276      * @throws  ReadOnlyBufferException
277      *          If this buffer is backed by an array but is read-only
278      *
279      * @throws  UnsupportedOperationException
280      *          If this buffer is not backed by an accessible array
281      *
282      */
283     // abstract Object array();
284 
285     /**
286      * Returns the offset within this buffer's backing array of the first
287      * element of the buffer&nbsp;&nbsp;<i>(optional operation)</i>.
288      *
289      * <p> If this buffer is backed by an array then buffer position <i>p</i>
290      * corresponds to array index <i>p</i>&nbsp;+&nbsp;<tt>arrayOffset()</tt>.
291      *
292      * <p> Invoke the {@link #hasArray hasArray} method before invoking this
293      * method in order to ensure that this buffer has an accessible backing
294      * array.  </p>
295      *
296      * @return  The offset within this buffer's array
297      *          of the first element of the buffer
298      *
299      * @throws  ReadOnlyBufferException
300      *          If this buffer is backed by an array but is read-only
301      *
302      * @throws  UnsupportedOperationException
303      *          If this buffer is not backed by an accessible array
304      *
305      */
306     abstract int arrayOffset();
307 
308     /**
309      * Tells whether or not this buffer is
310      * <a href="ByteBuffer.html#direct"><i>direct</i></a>.
311      *
312      * @return  <tt>true</tt> if, and only if, this buffer is direct
313      *
314      */
315     abstract bool isDirect();
316 
317 
318     // -- Package-private methods for bounds checking, etc. --
319 
320     /**
321      * Checks the current position against the limit, throwing a {@link
322      * BufferUnderflowException} if it is not smaller than the limit, and then
323      * increments the position.
324      *
325      * @return  The current position value, before it is incremented
326      */
327     final int nextGetIndex() {                          // package-private
328         if (_position >= _limit)
329             throw new BufferUnderflowException(format("limit=%d, position=%d", _limit, _position));
330         return _position++;
331     }
332 
333     final int nextGetIndex(int nb) {                    // package-private
334         if (_limit - _position < nb)
335             throw new BufferUnderflowException(format("limit=%d, position=%d, nb=%d", _limit, _position, nb));
336         int p = _position;
337         _position += nb;
338         return p;
339     }
340 
341     /**
342      * Checks the current position against the limit, throwing a {@link
343      * BufferOverflowException} if it is not smaller than the limit, and then
344      * increments the position.
345      *
346      * @return  The current position value, before it is incremented
347      */
348     final int nextPutIndex() {                          // package-private
349         if (_position >= _limit)
350             throw new BufferOverflowException(format("limit=%d, position=%d", _limit, _position));
351         return _position++;
352     }
353 
354     final int nextPutIndex(int nb) {                    // package-private
355         if (_limit - _position < nb)
356             throw new BufferOverflowException(format("limit=%d, position=%d, nb=%d", _limit, _position, nb));
357         int p = _position;
358         _position += nb;
359         return p;
360     }
361 
362     /**
363      * Checks the given index against the limit, throwing an {@link
364      * IndexOutOfBoundsException} if it is not smaller than the limit
365      * or is smaller than zero.
366      */
367     final int checkIndex(int i) {                       // package-private
368         if ((i < 0) || (i >= _limit))
369             throw new IndexOutOfBoundsException("Out of range");
370         return i;
371     }
372 
373     final int checkIndex(int i, int nb) {               // package-private
374         if ((i < 0) || (nb > _limit - i))
375             throw new IndexOutOfBoundsException("Out of range");
376         return i;
377     }
378 
379     final int markValue() {                             // package-private
380         return _mark;
381     }
382 
383     final void truncate() {                             // package-private
384         _mark = -1;
385         _position = 0;
386         _limit = 0;
387         _capacity = 0;
388     }
389 
390     final void discardMark() {                          // package-private
391         _mark = -1;
392     }
393 
394     static void checkBounds(int off, int len, int size) { // package-private
395         if ((off | len | (off + len) | (size - (off + len))) < 0)
396             throw new IndexOutOfBoundsException("Out of range");
397     }
398 }