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.collection.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 {@link
185      * java.nio.ByteBuffer#compact compact} method when transferring data from
186      * one place to another.  </p>
187      *
188      * @return  This buffer
189      */
190     final Buffer flip() {
191         _limit = _position;
192         _position = 0;
193         _mark = -1;
194         return this;
195     }
196 
197     /**
198      * Rewinds this buffer.  The position is set to zero and the mark is
199      * discarded.
200      *
201      * <p> Invoke this method before a sequence of channel-write or <i>get</i>
202      * operations, assuming that the limit has already been set
203      * appropriately.  For example:
204      *
205      * <blockquote><pre>
206      * out.write(buf);    // Write remaining data
207      * buf.rewind();      // Rewind buffer
208      * buf.get(array);    // Copy data into array</pre></blockquote>
209      *
210      * @return  This buffer
211      */
212     final Buffer rewind() {
213         _position = 0;
214         _mark = -1;
215         return this;
216     }
217 
218     /**
219      * Returns the number of elements between the current position and the
220      * limit.
221      *
222      * @return  The number of elements remaining in this buffer
223      */
224     final int remaining() {
225         return _limit - _position;
226     }
227 
228     /**
229      * Tells whether there are any elements between the current position and
230      * the limit.
231      *
232      * @return  <tt>true</tt> if, and only if, there is at least one element
233      *          remaining in this buffer
234      */
235     final bool hasRemaining() {
236         return _position < _limit;
237     }
238 
239     /**
240      * Tells whether or not this buffer is read-only.
241      *
242      * @return  <tt>true</tt> if, and only if, this buffer is read-only
243      */
244     abstract bool isReadOnly();
245 
246     /**
247      * Tells whether or not this buffer is backed by an accessible
248      * array.
249      *
250      * <p> If this method returns <tt>true</tt> then the {@link #array() array}
251      * and {@link #arrayOffset() arrayOffset} methods may safely be invoked.
252      * </p>
253      *
254      * @return  <tt>true</tt> if, and only if, this buffer
255      *          is backed by an array and is not read-only
256      *
257      */
258     abstract bool hasArray();
259 
260     /**
261      * Returns the array that backs this
262      * buffer&nbsp;&nbsp;<i>(optional operation)</i>.
263      *
264      * <p> This method is intended to allow array-backed buffers to be
265      * passed to native code more efficiently. Concrete subclasses
266      * provide more strongly-typed return values for this method.
267      *
268      * <p> Modifications to this buffer's content will cause the returned
269      * array's content to be modified, and vice versa.
270      *
271      * <p> Invoke the {@link #hasArray hasArray} method before invoking this
272      * method in order to ensure that this buffer has an accessible backing
273      * array.  </p>
274      *
275      * @return  The array that backs this buffer
276      *
277      * @throws  ReadOnlyBufferException
278      *          If this buffer is backed by an array but is read-only
279      *
280      * @throws  UnsupportedOperationException
281      *          If this buffer is not backed by an accessible array
282      *
283      */
284     // abstract Object array();
285 
286     /**
287      * Returns the offset within this buffer's backing array of the first
288      * element of the buffer&nbsp;&nbsp;<i>(optional operation)</i>.
289      *
290      * <p> If this buffer is backed by an array then buffer position <i>p</i>
291      * corresponds to array index <i>p</i>&nbsp;+&nbsp;<tt>arrayOffset()</tt>.
292      *
293      * <p> Invoke the {@link #hasArray hasArray} method before invoking this
294      * method in order to ensure that this buffer has an accessible backing
295      * array.  </p>
296      *
297      * @return  The offset within this buffer's array
298      *          of the first element of the buffer
299      *
300      * @throws  ReadOnlyBufferException
301      *          If this buffer is backed by an array but is read-only
302      *
303      * @throws  UnsupportedOperationException
304      *          If this buffer is not backed by an accessible array
305      *
306      */
307     abstract int arrayOffset();
308 
309     /**
310      * Tells whether or not this buffer is
311      * <a href="ByteBuffer.html#direct"><i>direct</i></a>.
312      *
313      * @return  <tt>true</tt> if, and only if, this buffer is direct
314      *
315      */
316     abstract bool isDirect();
317 
318 
319     // -- Package-private methods for bounds checking, etc. --
320 
321     /**
322      * Checks the current position against the limit, throwing a {@link
323      * BufferUnderflowException} if it is not smaller than the limit, and then
324      * increments the position.
325      *
326      * @return  The current position value, before it is incremented
327      */
328     final int nextGetIndex() {                          // package-private
329         if (_position >= _limit)
330             throw new BufferUnderflowException(format("limit=%d, position=%d", _limit, _position));
331         return _position++;
332     }
333 
334     final int nextGetIndex(int nb) {                    // package-private
335         if (_limit - _position < nb)
336             throw new BufferUnderflowException(format("limit=%d, position=%d, nb=%d", _limit, _position, nb));
337         int p = _position;
338         _position += nb;
339         return p;
340     }
341 
342     /**
343      * Checks the current position against the limit, throwing a {@link
344      * BufferOverflowException} if it is not smaller than the limit, and then
345      * increments the position.
346      *
347      * @return  The current position value, before it is incremented
348      */
349     final int nextPutIndex() {                          // package-private
350         if (_position >= _limit)
351             throw new BufferOverflowException(format("limit=%d, position=%d", _limit, _position));
352         return _position++;
353     }
354 
355     final int nextPutIndex(int nb) {                    // package-private
356         if (_limit - _position < nb)
357             throw new BufferOverflowException(format("limit=%d, position=%d, nb=%d", _limit, _position, nb));
358         int p = _position;
359         _position += nb;
360         return p;
361     }
362 
363     /**
364      * Checks the given index against the limit, throwing an {@link
365      * IndexOutOfBoundsException} if it is not smaller than the limit
366      * or is smaller than zero.
367      */
368     final int checkIndex(int i) {                       // package-private
369         if ((i < 0) || (i >= _limit))
370             throw new IndexOutOfBoundsException("Out of range");
371         return i;
372     }
373 
374     final int checkIndex(int i, int nb) {               // package-private
375         if ((i < 0) || (nb > _limit - i))
376             throw new IndexOutOfBoundsException("Out of range");
377         return i;
378     }
379 
380     final int markValue() {                             // package-private
381         return _mark;
382     }
383 
384     final void truncate() {                             // package-private
385         _mark = -1;
386         _position = 0;
387         _limit = 0;
388         _capacity = 0;
389     }
390 
391     final void discardMark() {                          // package-private
392         _mark = -1;
393     }
394 
395     static void checkBounds(int off, int len, int size) { // package-private
396         if ((off | len | (off + len) | (size - (off + len))) < 0)
397             throw new IndexOutOfBoundsException("Out of range");
398     }
399 }