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.BufferUtils;
13 
14 import hunt.text.Common;
15 import hunt.text.StringBuilder;
16 
17 import hunt.util.Common;
18 import hunt.collection.ByteBuffer;
19 import hunt.collection.Collection;
20 import hunt.collection.Enumeration;
21 import hunt.collection.HeapByteBuffer;
22 import hunt.collection.List;
23 
24 import hunt.Exceptions;
25 import hunt.util.ConverterUtils;
26 
27 import std.conv;
28 import std.range;
29 
30 
31 /**
32  * Buffer utility methods.
33  * <p>
34  * The standard JVM {@link ByteBuffer} can exist in two modes: In fill mode the
35  * valid data is between 0 and pos; In flush mode the valid data is between the
36  * pos and the limit. The various ByteBuffer methods assume a mode and some of
37  * them will switch or enforce a mode: Allocate and clear set fill mode; flip
38  * and compact switch modes; read and write assume fill and flush modes. This
39  * duality can result in confusing code such as:
40  * </p>
41  * <p>
42  * <pre>
43  * buffer.clear();
44  * channel.write(buffer);
45  * </pre>
46  * <p>
47  * Which looks as if it should write no data, but in fact writes the buffer
48  * worth of garbage.
49  * </p>
50  * <p>
51  * The BufferUtils class provides a set of utilities that operate on the
52  * convention that ByteBuffers will always be left, passed in an API or returned
53  * from a method in the flush mode - ie with valid data between the pos and
54  * limit. This convention is adopted so as to avoid confusion as to what state a
55  * buffer is in and to avoid excessive copying of data that can result with the
56  * usage of compress.
57  * </p>
58  * <p>
59  * Thus this class provides alternate implementations of {@link #allocate(int)},
60  * {@link #allocateDirect(int)} and {@link #clear(ByteBuffer)} that leave the
61  * buffer in flush mode. Thus the following tests will pass:
62  * </p>
63  * <p>
64  * <pre>
65  * ByteBuffer buffer = BufferUtils.allocate(1024);
66  * assert (buffer.remaining() == 0);
67  * BufferUtils.clear(buffer);
68  * assert (buffer.remaining() == 0);
69  * </pre>
70  * <p>
71  * If the BufferUtils methods {@link #fill(ByteBuffer, byte[], int, int)},
72  * {@link #append(ByteBuffer, byte[], int, int)} or
73  * {@link #put(ByteBuffer, ByteBuffer)} are used, then the caller does not need
74  * to explicitly switch the buffer to fill mode. If the caller wishes to use
75  * other ByteBuffer bases libraries to fill a buffer, then they can use explicit
76  * calls of #flipToFill(ByteBuffer) and #flipToFlush(ByteBuffer, int) to change
77  * modes. Note because this convention attempts to avoid the copies of compact,
78  * the position is not set to zero on each fill cycle and so its value must be
79  * remembered:
80  * </p>
81  * <p>
82  * <pre>
83  * int pos = BufferUtils.flipToFill(buffer);
84  * try {
85  * 	buffer.put(data);
86  * } finally {
87  * 	flipToFlush(buffer, pos);
88  * }
89  * </pre>
90  * <p>
91  * The flipToFill method will effectively clear the buffer if it is empty and
92  * will compact the buffer if there is no space.
93  * </p>
94  */
95 class BufferUtils {
96 
97     __gshared ByteBuffer EMPTY_BUFFER;
98     __gshared ByteBuffer[] EMPTY_BYTE_BUFFER_ARRAY;
99 
100     enum int TEMP_BUFFER_SIZE = 4096;
101     enum byte SPACE = 0x20;
102     enum byte MINUS = '-';
103     enum byte[] DIGIT = [
104             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
105         ];
106 
107     shared static this() {
108         EMPTY_BUFFER = new HeapByteBuffer(new byte[0], 0, 0); 
109         EMPTY_BYTE_BUFFER_ARRAY = new ByteBuffer[0];
110     }
111     /* ------------------------------------------------------------ */
112 
113     /**
114      * Allocate ByteBuffer in flush mode.
115      * The position and limit will both be zero, indicating that the buffer is
116      * empty and must be flipped before any data is put to it.
117      *
118      * @param capacity capacity of the allocated ByteBuffer
119      * @return Buffer
120      */
121     static ByteBuffer allocate(size_t capacity) {
122         ByteBuffer buf = new HeapByteBuffer(cast(int)capacity, cast(int)capacity);
123         // FIXME: Needing refactor or cleanup -@zxp at 3/20/2019, 5:49:08 PM
124         // 
125         // buf.limit(0);
126         return buf;
127     }
128 
129     /* ------------------------------------------------------------ */
130 
131     /**
132      * Allocate ByteBuffer in flush mode.
133      * The position and limit will both be zero, indicating that the buffer is
134      * empty and in flush mode.
135      *
136      * @param capacity capacity of the allocated ByteBuffer
137      * @return Buffer
138      */
139     static ByteBuffer allocateDirect(int capacity) {
140         // FIXME: Needing refactor or cleanup -@zxp at 8/17/2019, 1:03:57 PM        
141         // 
142         ByteBuffer buf = new HeapByteBuffer(capacity, capacity); // DirectByteBuffer(capacity); 
143         // buf.limit(0);
144         return buf;
145     }
146 
147 
148     /**
149      * Wraps a byte array into a buffer.
150      *
151      * <p> The new buffer will be backed by the given byte array;
152      * that is, modifications to the buffer will cause the array to be modified
153      * and vice versa.  The new buffer's capacity will be
154      * <tt>array.length</tt>, its position will be <tt>offset</tt>, its limit
155      * will be <tt>offset + length</tt>, and its mark will be undefined.  Its
156      * {@link #array backing array} will be the given array, and
157      * its {@link #arrayOffset array offset} will be zero.  </p>
158      *
159      * @param  array
160      *         The array that will back the new buffer
161      *
162      * @param  offset
163      *         The offset of the subarray to be used; must be non-negative and
164      *         no larger than <tt>array.length</tt>.  The new buffer's position
165      *         will be set to this value.
166      *
167      * @param  length
168      *         The length of the subarray to be used;
169      *         must be non-negative and no larger than
170      *         <tt>array.length - offset</tt>.
171      *         The new buffer's limit will be set to <tt>offset + length</tt>.
172      *
173      * @return  The new byte buffer
174      *
175      * @throws  IndexOutOfBoundsException
176      *          If the preconditions on the <tt>offset</tt> and <tt>length</tt>
177      *          parameters do not hold
178      */
179     static ByteBuffer wrap(byte[] array, int offset, int length)
180     {
181         try {
182             return new HeapByteBuffer(array, offset, length);
183         } catch (IllegalArgumentException x) {
184             throw new IndexOutOfBoundsException("");
185         }
186     }
187 
188     /**
189      * Wraps a byte array into a buffer.
190      *
191      * <p> The new buffer will be backed by the given byte array;
192      * that is, modifications to the buffer will cause the array to be modified
193      * and vice versa.  The new buffer's capacity and limit will be
194      * <tt>array.length</tt>, its position will be zero, and its mark will be
195      * undefined.  Its {@link #array backing array} will be the
196      * given array, and its {@link #arrayOffset array offset>} will
197      * be zero.  </p>
198      *
199      * @param  array
200      *         The array that will back this buffer
201      *
202      * @return  The new byte buffer
203      */
204     static ByteBuffer wrap(byte[] array) {
205         return wrap(array, 0, cast(int)array.length);
206     }
207 
208 
209     /* ------------------------------------------------------------ */
210 
211     /**
212      * Clear the buffer to be empty in flush mode.
213      * The position and limit are set to 0;
214      *
215      * @param buffer The buffer to clear.
216      */
217     static void clear(ByteBuffer buffer) {
218         if (buffer !is null) {
219             buffer.position(0);
220             buffer.limit(0);
221         }
222     }
223 
224     /* ------------------------------------------------------------ */
225 
226     /**
227      * Clear the buffer to be empty in fill mode.
228      * The position is set to 0 and the limit is set to the capacity.
229      *
230      * @param buffer The buffer to clear.
231      */
232     static void clearToFill(ByteBuffer buffer) {
233         if (buffer !is null) {
234             buffer.position(0);
235             buffer.limit(buffer.capacity());
236         }
237     }
238 
239     /* ------------------------------------------------------------ */
240 
241     /**
242      * Flip the buffer to fill mode.
243      * The position is set to the first unused position in the buffer
244      * (the old limit) and the limit is set to the capacity.
245      * If the buffer is empty, then this call is effectively {@link #clearToFill(ByteBuffer)}.
246      * If there is no unused space to fill, a {@link ByteBuffer#compact()} is done to attempt
247      * to create space.
248      * <p>
249      * This method is used as a replacement to {@link ByteBuffer#compact()}.
250      *
251      * @param buffer The buffer to flip
252      * @return The position of the valid data before the flipped position. This value should be
253      * passed to a subsequent call to {@link #flipToFlush(ByteBuffer, int)}
254      */
255     static int flipToFill(ByteBuffer buffer) {
256         int position = buffer.position();
257         int limit = buffer.limit();
258         if (position == limit) {
259             buffer.position(0);
260             buffer.limit(buffer.capacity());
261             return 0;
262         }
263 
264         int capacity = buffer.capacity();
265         if (limit == capacity) {
266             buffer.compact();
267             return 0;
268         }
269 
270         buffer.position(limit);
271         buffer.limit(capacity);
272         return position;
273     }
274 
275     /* ------------------------------------------------------------ */
276 
277     /**
278      * Flip the buffer to Flush mode.
279      * The limit is set to the first unused byte(the old position) and
280      * the position is set to the passed position.
281      * <p>
282      * This method is used as a replacement of {@link Buffer#flip()}.
283      *
284      * @param buffer   the buffer to be flipped
285      * @param position The position of valid data to flip to. This should
286      *                 be the return value of the previous call to {@link #flipToFill(ByteBuffer)}
287      */
288     static void flipToFlush(ByteBuffer buffer, int position) {
289         buffer.limit(buffer.position());
290         buffer.position(position);
291     }
292 
293     /* ------------------------------------------------------------ */
294 
295     /**
296      * Convert a ByteBuffer to a byte array.
297      *
298      * @param buffer The buffer to convert in flush mode. The buffer is not altered.
299      * @return An array of bytes duplicated from the buffer.
300      */
301     static byte[] toArray(ByteBuffer buffer, bool canDuplicate = true) {
302         if (buffer.hasArray()) {
303             byte[] array = buffer.array();
304             int from = buffer.arrayOffset() + buffer.position();
305             if (canDuplicate)
306                 return array[from .. from + buffer.remaining()].dup;
307             else
308                 return array[from .. from + buffer.remaining()];
309         } else {
310             byte[] to = new byte[buffer.remaining()];
311             buffer.slice().get(to);
312             return to;
313         }
314     }
315 
316     /* ------------------------------------------------------------ */
317 
318     /**
319      * Check for an empty or null buffer.
320      *
321      * @param buf the buffer to check
322      * @return true if the buffer is null or empty.
323      */
324     static bool isEmpty(ByteBuffer buf) {
325         return buf is null || buf.remaining() == 0;
326     }
327 
328     /* ------------------------------------------------------------ */
329 
330     /**
331      * Check for a non null and non empty buffer.
332      *
333      * @param buf the buffer to check
334      * @return true if the buffer is not null and not empty.
335      */
336     static bool hasContent(ByteBuffer buf) {
337         return buf !is null && buf.remaining() > 0;
338     }
339 
340     /* ------------------------------------------------------------ */
341 
342     /**
343      * Check for a non null and full buffer.
344      *
345      * @param buf the buffer to check
346      * @return true if the buffer is not null and the limit equals the capacity.
347      */
348     static bool isFull(ByteBuffer buf) {
349         return buf !is null && buf.limit() == buf.capacity();
350     }
351 
352     /* ------------------------------------------------------------ */
353 
354     /**
355      * Get remaining from null checked buffer
356      *
357      * @param buffer The buffer to get the remaining from, in flush mode.
358      * @return 0 if the buffer is null, else the bytes remaining in the buffer.
359      */
360     static int length(ByteBuffer buffer) {
361         return buffer is null ? 0 : buffer.remaining();
362     }
363 
364     /* ------------------------------------------------------------ */
365 
366     /**
367      * Get the space from the limit to the capacity
368      *
369      * @param buffer the buffer to get the space from
370      * @return space
371      */
372     static int space(ByteBuffer buffer) {
373         if (buffer is null)
374             return 0;
375         return buffer.capacity() - buffer.limit();
376     }
377 
378     /* ------------------------------------------------------------ */
379 
380     /**
381      * Compact the buffer
382      *
383      * @param buffer the buffer to compact
384      * @return true if the compact made a full buffer have space
385      */
386     static bool compact(ByteBuffer buffer) {
387         if (buffer.position() == 0)
388             return false;
389         bool full = buffer.limit() == buffer.capacity();
390         buffer.compact().flip();
391         return full && buffer.limit() < buffer.capacity();
392     }
393 
394     /* ------------------------------------------------------------ */
395 
396     /**
397      * Put data from one buffer into another, avoiding over/under flows
398      *
399      * @param from Buffer to take bytes from in flush mode
400      * @param to   Buffer to put bytes to in fill mode.
401      * @return number of bytes moved
402      */
403     static int put(ByteBuffer from, ByteBuffer to) {
404         int put;
405         int remaining = from.remaining();
406         if (remaining > 0) {
407             if (remaining <= to.remaining()) {
408                 to.put(from);
409                 put = remaining;
410                 from.position(from.limit());
411             } else if (from.hasArray()) {
412                 put = to.remaining();
413                 to.put(from.array(), from.arrayOffset() + from.position(), put);
414                 from.position(from.position() + put);
415             } else {
416                 put = to.remaining();
417                 ByteBuffer slice = from.slice();
418                 slice.limit(put);
419                 to.put(slice);
420                 from.position(from.position() + put);
421             }
422         } else
423             put = 0;
424 
425         return put;
426     }
427 
428     /* ------------------------------------------------------------ */
429 
430     /**
431      * Put data from one buffer into another, avoiding over/under flows
432      *
433      * @param from Buffer to take bytes from in flush mode
434      * @param to   Buffer to put bytes to in flush mode. The buffer is flipToFill before the put and flipToFlush after.
435      * @return number of bytes moved
436      * @deprecated use {@link #append(ByteBuffer, ByteBuffer)}
437      */
438     static int flipPutFlip(ByteBuffer from, ByteBuffer to) {
439         return append(to, from);
440     }
441 
442     /* ------------------------------------------------------------ */
443 
444     /**
445      * Append bytes to a buffer.
446      *
447      * @param to  Buffer is flush mode
448      * @param b   bytes to append
449      * @param off offset into byte
450      * @param len length to append
451      * @throws BufferOverflowException if unable to append buffer due to space limits
452      */
453     static void append(ByteBuffer to, byte[] b, int off, int len) {
454         int pos = flipToFill(to);
455         try {
456             to.put(b, off, len);
457         } finally {
458             flipToFlush(to, pos);
459         }
460     }
461 
462     /* ------------------------------------------------------------ */
463 
464     /**
465      * Appends a byte to a buffer
466      *
467      * @param to Buffer is flush mode
468      * @param b  byte to append
469      */
470     static void append(ByteBuffer to, byte b) {
471         int pos = flipToFill(to);
472         try {
473             to.put(b);
474         } finally {
475             flipToFlush(to, pos);
476         }
477     }
478 
479     /* ------------------------------------------------------------ */
480 
481     /**
482      * Appends a buffer to a buffer
483      *
484      * @param to Buffer is flush mode
485      * @param b  buffer to append
486      * @return The position of the valid data before the flipped position.
487      */
488     static int append(ByteBuffer to, ByteBuffer b) {
489         int pos = flipToFill(to);
490         try {
491             return put(b, to);
492         } finally {
493             flipToFlush(to, pos);
494         }
495     }
496 
497     /* ------------------------------------------------------------ */
498 
499     /**
500      * Like append, but does not throw {@link BufferOverflowException}
501      *
502      * @param to  Buffer is flush mode
503      * @param b   bytes to fill
504      * @param off offset into byte
505      * @param len length to fill
506      * @return The position of the valid data before the flipped position.
507      */
508     static int fill(ByteBuffer to, byte[] b, int off, int len) {
509         int pos = flipToFill(to);
510         try {
511             int remaining = to.remaining();
512             int take = remaining < len ? remaining : len;
513             to.put(b, off, take);
514             return take;
515         } finally {
516             flipToFlush(to, pos);
517         }
518     }
519 
520     // /* ------------------------------------------------------------ */
521     // static void readFrom(File file, ByteBuffer buffer) throws IOException {
522     //     try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
523     //         FileChannel channel = raf.getChannel();
524     //         long needed = raf.length();
525 
526     //         while (needed > 0 && buffer.hasRemaining())
527     //             needed = needed - channel.read(buffer);
528     //     }
529     // }
530 
531     // /* ------------------------------------------------------------ */
532     // static void readFrom(InputStream is, int needed, ByteBuffer buffer) throws IOException {
533     //     ByteBuffer tmp = allocate(8192);
534 
535     //     while (needed > 0 && buffer.hasRemaining()) {
536     //         int l = is.read(tmp.array(), 0, 8192);
537     //         if (l < 0)
538     //             break;
539     //         tmp.position(0);
540     //         tmp.limit(l);
541     //         buffer.put(tmp);
542     //     }
543     // }
544 
545     /* ------------------------------------------------------------ */
546     static void writeTo(ByteBuffer buffer, ref Appender!(byte[]) ot) {
547         if (buffer.hasArray()) {
548             // out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
549             auto array = buffer.array();
550             int from = buffer.arrayOffset() + buffer.position();
551             int to = from + buffer.remaining();
552             ot.put(array[from .. to]);
553 
554             // update buffer position, in way similar to non-array version of writeTo
555             buffer.position = buffer.position() + buffer.remaining();
556         } else {
557             implementationMissing();
558             // byte[] bytes = new byte[TEMP_BUFFER_SIZE];
559             // while (buffer.hasRemaining()) {
560             //     int byteCountToWrite = std.algorithm.min(buffer.remaining(), TEMP_BUFFER_SIZE);
561             //     buffer.get(bytes, 0, byteCountToWrite);
562             //     out.write(bytes, 0, byteCountToWrite);
563             // }
564         }
565     }
566 
567     /* ------------------------------------------------------------ */
568 
569     /**
570      * Convert the buffer to an ISO-8859-1 string
571      *
572      * @param buffer The buffer to convert in flush mode. The buffer is unchanged
573      * @return The buffer as a string.
574      */
575     static string toString(ByteBuffer buffer) {
576         if (buffer is null || buffer.remaining() == 0)
577             return null;
578 
579         byte[] array = buffer.hasArray() ? buffer.array() : null;
580         if (array is null) {
581             return cast(string) buffer.array[0 .. buffer.remaining()];
582         } else {
583             int start = buffer.arrayOffset() + buffer.position();
584             int end = start + buffer.remaining();
585             return cast(string) array[start .. end];
586         }
587     }
588 
589     // /* ------------------------------------------------------------ */
590 
591     // /**
592     //  * Convert the buffer to an UTF-8 string
593     //  *
594     //  * @param buffer The buffer to convert in flush mode. The buffer is unchanged
595     //  * @return The buffer as a string.
596     //  */
597     // static string toUTF8String(ByteBuffer buffer) {
598     //     return toString(buffer, StandardCharsets.UTF_8);
599     // }
600 
601     // /* ------------------------------------------------------------ */
602 
603     // /**
604     //  * Convert the buffer to an ISO-8859-1 string
605     //  *
606     //  * @param buffer  The buffer to convert in flush mode. The buffer is unchanged
607     //  * @param charset The {@link Charset} to use to convert the bytes
608     //  * @return The buffer as a string.
609     //  */
610     // static string toString(ByteBuffer buffer, Charset charset) {
611     //     if (buffer is null)
612     //         return null;
613     //     byte[] array = buffer.hasArray() ? buffer.array() : null;
614     //     if (array is null) {
615     //         byte[] to = new byte[buffer.remaining()];
616     //         buffer.slice().get(to);
617     //         return new string(to, 0, to.length, charset);
618     //     }
619     //     return new string(array, buffer.arrayOffset() + buffer.position(), buffer.remaining(), charset);
620     // }
621 
622     // /* ------------------------------------------------------------ */
623 
624     /**
625      * Convert a partial buffer to a string.
626      *
627      * @param buffer   the buffer to convert
628      * @param position The position in the buffer to start the string from
629      * @param length   The length of the buffer
630      * @param charset  The {@link Charset} to use to convert the bytes
631      * @return The buffer as a string.
632      */
633     static string toString(ByteBuffer buffer, int position, int length) {
634         if (buffer is null)
635             return null;
636         byte[] array = buffer.hasArray() ? buffer.array() : null;
637         if (array is null) {
638             ByteBuffer ro = buffer.asReadOnlyBuffer();
639             ro.position(position);
640             ro.limit(position + length);
641             byte[] to = new byte[length];
642             ro.get(to);
643             return cast(string) to;
644         }
645         int startIndex = buffer.arrayOffset() + position;
646         int endIndex = startIndex + length;
647         return cast(string) array[startIndex .. endIndex];
648     }
649 
650     // /* ------------------------------------------------------------ */
651 
652     /**
653      * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
654      *
655      * @param buffer A buffer containing an integer in flush mode. The position is not changed.
656      * @return an int
657      */
658     static int toInt(ByteBuffer buffer) {
659         return toInt(buffer, buffer.position(), buffer.remaining());
660     }
661 
662     /* ------------------------------------------------------------ */
663 
664     /**
665      * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an
666      * IllegalArgumentException is thrown
667      *
668      * @param buffer   A buffer containing an integer in flush mode. The position is not changed.
669      * @param position the position in the buffer to start reading from
670      * @param length   the length of the buffer to use for conversion
671      * @return an int of the buffer bytes
672      */
673     static int toInt(ByteBuffer buffer, int position, int length) {
674         int val = 0;
675         bool started = false;
676         bool minus = false;
677 
678         int limit = position + length;
679 
680         if (length <= 0)
681             throw new NumberFormatException(toString(buffer, position, length));
682 
683         for (int i = position; i < limit; i++) {
684             byte b = buffer.get(i);
685             if (b <= SPACE) {
686                 if (started)
687                     break;
688             } else if (b >= '0' && b <= '9') {
689                 val = val * 10 + (b - '0');
690                 started = true;
691             } else if (b == MINUS && !started) {
692                 minus = true;
693             } else
694                 break;
695         }
696 
697         if (started)
698             return minus ? (-val) : val;
699         throw new NumberFormatException(toString(buffer));
700     }
701 
702     /* ------------------------------------------------------------ */
703 
704     /**
705      * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
706      *
707      * @param buffer A buffer containing an integer in flush mode. The position is updated.
708      * @return an int
709      */
710     static int takeInt(ByteBuffer buffer) {
711         int val = 0;
712         bool started = false;
713         bool minus = false;
714         int i;
715         for (i = buffer.position(); i < buffer.limit(); i++) {
716             byte b = buffer.get(i);
717             if (b <= SPACE) {
718                 if (started)
719                     break;
720             } else if (b >= '0' && b <= '9') {
721                 val = val * 10 + (b - '0');
722                 started = true;
723             } else if (b == MINUS && !started) {
724                 minus = true;
725             } else
726                 break;
727         }
728 
729         if (started) {
730             buffer.position(i);
731             return minus ? (-val) : val;
732         }
733         throw new NumberFormatException(toString(buffer));
734     }
735 
736     /**
737      * Convert buffer to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
738      *
739      * @param buffer A buffer containing an integer in flush mode. The position is not changed.
740      * @return an int
741      */
742     static long toLong(ByteBuffer buffer) {
743         long val = 0;
744         bool started = false;
745         bool minus = false;
746 
747         for (int i = buffer.position(); i < buffer.limit(); i++) {
748             byte b = buffer.get(i);
749             if (b <= SPACE) {
750                 if (started)
751                     break;
752             } else if (b >= '0' && b <= '9') {
753                 val = val * 10L + (b - '0');
754                 started = true;
755             } else if (b == MINUS && !started) {
756                 minus = true;
757             } else
758                 break;
759         }
760 
761         if (started)
762             return minus ? (-val) : val;
763         throw new NumberFormatException(toString(buffer));
764     }
765 
766     static void putHexInt(ByteBuffer buffer, int n) {
767         if (n < 0) {
768             buffer.put(cast(byte) '-');
769 
770             if (n == int.min) {
771                 buffer.put(cast(byte)(0x7f & '8'));
772                 buffer.put(cast(byte)(0x7f & '0'));
773                 buffer.put(cast(byte)(0x7f & '0'));
774                 buffer.put(cast(byte)(0x7f & '0'));
775                 buffer.put(cast(byte)(0x7f & '0'));
776                 buffer.put(cast(byte)(0x7f & '0'));
777                 buffer.put(cast(byte)(0x7f & '0'));
778                 buffer.put(cast(byte)(0x7f & '0'));
779 
780                 return;
781             }
782             n = -n;
783         }
784 
785         if (n < 0x10) {
786             buffer.put(DIGIT[n]);
787         } else {
788             bool started = false;
789             // This assumes constant time int arithmatic
790             foreach (int hexDivisor; hexDivisors) {
791                 if (n < hexDivisor) {
792                     if (started)
793                         buffer.put(cast(byte) '0');
794                     continue;
795                 }
796 
797                 started = true;
798                 int d = n / hexDivisor;
799                 buffer.put(DIGIT[d]);
800                 n = n - d * hexDivisor;
801             }
802         }
803     }
804 
805     /* ------------------------------------------------------------ */
806     static void putDecInt(ByteBuffer buffer, int n) {
807         if (n < 0) {
808             buffer.put(cast(byte) '-');
809 
810             if (n == int.min) {
811                 buffer.put(cast(byte) '2');
812                 n = 147483648;
813             } else
814                 n = -n;
815         }
816 
817         if (n < 10) {
818             buffer.put(DIGIT[n]);
819         } else {
820             bool started = false;
821             // This assumes constant time int arithmatic
822             foreach (int decDivisor; decDivisors) {
823                 if (n < decDivisor) {
824                     if (started)
825                         buffer.put(cast(byte) '0');
826                     continue;
827                 }
828 
829                 started = true;
830                 int d = n / decDivisor;
831                 buffer.put(DIGIT[d]);
832                 n = n - d * decDivisor;
833             }
834         }
835     }
836 
837     static void putDecLong(ByteBuffer buffer, long n) {
838         if (n < 0) {
839             buffer.put(cast(byte) '-');
840 
841             if (n == long.min) {
842                 buffer.put(cast(byte) '9');
843                 n = 223372036854775808L;
844             } else
845                 n = -n;
846         }
847 
848         if (n < 10) {
849             buffer.put(DIGIT[cast(int) n]);
850         } else {
851             bool started = false;
852             // This assumes constant time int arithmatic
853             foreach (long aDecDivisorsL; decDivisorsL) {
854                 if (n < aDecDivisorsL) {
855                     if (started)
856                         buffer.put(cast(byte) '0');
857                     continue;
858                 }
859 
860                 started = true;
861                 long d = n / aDecDivisorsL;
862                 buffer.put(DIGIT[cast(int) d]);
863                 n = n - d * aDecDivisorsL;
864             }
865         }
866     }
867 
868     static ByteBuffer toBuffer(int value) {
869         ByteBuffer buf = allocate(32);
870         putDecInt(buf, value);
871         return buf;
872     }
873 
874     static ByteBuffer toBuffer(long value) {
875         ByteBuffer buf = allocate(32);
876         putDecLong(buf, value);
877         return buf;
878     }
879 
880     static ByteBuffer toBuffer(string s) {
881         return toBuffer(cast(byte[]) s.dup);
882     }
883 
884     // static ByteBuffer toBuffer(string s, Charset charset) {
885     //     if (s is null)
886     //         return EMPTY_BUFFER;
887     //     return toBuffer(s.getBytes(charset));
888     // }
889 
890     /**
891      * Create a new ByteBuffer using provided byte array.
892      *
893      * @param array the byte array to back buffer with.
894      * @return ByteBuffer with provided byte array, in flush mode
895      */
896     static ByteBuffer toBuffer(byte[] array) {
897         if (array is null)
898             return EMPTY_BUFFER;
899         return toBuffer(array, 0, cast(int) array.length);
900     }
901 
902     /**
903      * Create a new ByteBuffer using the provided byte array.
904      *
905      * @param array  the byte array to use.
906      * @param offset the offset within the byte array to use from
907      * @param length the length in bytes of the array to use
908      * @return ByteBuffer with provided byte array, in flush mode
909      */
910     static ByteBuffer toBuffer(byte[] array, int offset, int length) {
911         if (array is null)
912             return EMPTY_BUFFER;
913         try {
914             return new HeapByteBuffer(array, offset, length);
915         } catch (IllegalArgumentException x) {
916             throw new IndexOutOfBoundsException("");
917         }
918     }
919 
920     static ByteBuffer toDirectBuffer(string s) {
921         if (s.empty)
922             return EMPTY_BUFFER;
923         byte[] bytes = cast(byte[]) s.dup;
924         // TODO: Tasks pending completion -@zxp at 8/25/2018, 3:11:29 PM
925         // ByteBuffer.allocateDirect(bytes.length); 
926         ByteBuffer buf = new HeapByteBuffer(cast(int) bytes.length, cast(int) bytes.length);
927         buf.put(bytes);
928         buf.flip();
929         return buf;
930         // return toDirectBuffer(s, "StandardCharsets.ISO_8859_1");
931     }
932 
933     // static ByteBuffer toDirectBuffer(string s, Charset charset) {
934     //     if (s is null)
935     //         return EMPTY_BUFFER;
936     //     byte[] bytes = s.getBytes(charset);
937     //     ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
938     //     buf.put(bytes);
939     //     buf.flip();
940     //     return buf;
941     // }
942 
943     // static ByteBuffer toMappedBuffer(File file) throws IOException {
944     //     try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
945     //         return channel.map(MapMode.READ_ONLY, 0, file.length());
946     //     }
947     // }
948 
949     // static bool isMappedBuffer(ByteBuffer buffer) {
950     //     if (!(buffer instanceof MappedByteBuffer))
951     //         return false;
952     //     MappedByteBuffer mapped = (MappedByteBuffer) buffer;
953 
954     //     try {
955     //         // Check if it really is a mapped buffer
956     //         mapped.isLoaded();
957     //         return true;
958     //     } catch (UnsupportedOperationException e) {
959     //         return false;
960     //     }
961     // }
962 
963     /* ------------------------------------------------------------ */
964 
965     /**
966      * Convert Buffer to string ID independent of content
967      */
968     private static void idString(ByteBuffer buffer, StringBuilder ot) {
969         // ot.append(typeof(buffer).stringof);
970         ot.append(typeid(buffer).name);
971         ot.append("@");
972         if (buffer.hasArray() && buffer.arrayOffset() == 4) {
973             ot.append('T');
974             byte[] array = buffer.array();
975             ConverterUtils.toHex(array[0], ot);
976             ConverterUtils.toHex(array[1], ot);
977             ConverterUtils.toHex(array[2], ot);
978             ConverterUtils.toHex(array[3], ot);
979         } else {
980             size_t hashCode = hashOf(buffer);
981             ot.append(to!string(hashCode, 16));
982             // ot.append(Integer.toHexString(System.identityHashCode(buffer)));
983         }
984     }
985 
986     /* ------------------------------------------------------------ */
987 
988     /**
989      * Convert Buffer to string ID independent of content
990      *
991      * @param buffer the buffet to generate a string ID from
992      * @return A string showing the buffer ID
993      */
994     static string toIDString(ByteBuffer buffer) {
995         StringBuilder buf = new StringBuilder();
996         idString(buffer, buf);
997         return buf.toString();
998     }
999 
1000     /* ------------------------------------------------------------ */
1001 
1002     static string toSummaryString(ByteBuffer buffer) {
1003         if (buffer is null)
1004             return "null";
1005         StringBuilder buf = new StringBuilder();
1006         buf.append("[p=");
1007         buf.append(buffer.position());
1008         buf.append(",l=");
1009         buf.append(buffer.limit());
1010         buf.append(",c=");
1011         buf.append(buffer.capacity());
1012         buf.append(",r=");
1013         buf.append(buffer.remaining());
1014         buf.append("]");
1015         return buf.toString();
1016     }
1017 
1018     static string toDetailString(ByteBuffer[] buffer) {
1019         StringBuilder builder = new StringBuilder();
1020         builder.append('[');
1021         for (int i = 0; i < buffer.length; i++) {
1022             if (i > 0)
1023                 builder.append(',');
1024             builder.append(toDetailString(buffer[i]));
1025         }
1026         builder.append(']');
1027         return builder.toString();
1028     }
1029 
1030     /**
1031      * Convert Buffer to a detail debug string of pointers and content
1032      *
1033      * @param buffer the buffer to generate a detail string from
1034      * @return A string showing the pointers and content of the buffer
1035      */
1036     static string toDetailString(ByteBuffer buffer) {
1037         if (buffer is null)
1038             return "null";
1039 
1040         StringBuilder buf = new StringBuilder();
1041         idString(buffer, buf);
1042         buf.append("[p=");
1043         buf.append(buffer.position());
1044         buf.append(",l=");
1045         buf.append(buffer.limit());
1046         buf.append(",c=");
1047         buf.append(buffer.capacity());
1048         buf.append(",r=");
1049         buf.append(buffer.remaining());
1050         buf.append("]={");
1051 
1052         appendDebugString(buf, buffer);
1053 
1054         buf.append("}");
1055 
1056         return buf.toString();
1057     }
1058 
1059     private static void appendDebugString(StringBuilder buf, ByteBuffer buffer) {
1060         try {
1061             for (int i = 0; i < buffer.position(); i++) {
1062                 appendContentChar(buf, buffer.get(i));
1063                 if (i == 16 && buffer.position() > 32) {
1064                     buf.append("...");
1065                     i = buffer.position() - 16;
1066                 }
1067             }
1068             buf.append("<<<");
1069             for (int i = buffer.position(); i < buffer.limit(); i++) {
1070                 appendContentChar(buf, buffer.get(i));
1071                 if (i == buffer.position() + 16 && buffer.limit() > buffer.position() + 32) {
1072                     buf.append("...");
1073                     i = buffer.limit() - 16;
1074                 }
1075             }
1076             buf.append(">>>");
1077 
1078             // int limit = buffer.limit();
1079             // buffer.limit(buffer.capacity());
1080             // for (int i = limit; i < buffer.capacity(); i++) {
1081             //     appendContentChar(buf, buffer.get(i));
1082             //     if (i == limit + 16 && buffer.capacity() > limit + 32) {
1083             //         buf.append("...");
1084             //         i = buffer.capacity() - 16;
1085             //     }
1086             // }
1087             // buffer.limit(limit);
1088         } catch (Exception x) {
1089             buf.append("!!concurrent mod!!");
1090         }
1091     }
1092 
1093     private static void appendContentChar(StringBuilder buf, byte b) {
1094         if (b == '\\')
1095             buf.append("\\\\");
1096         else if (b >= ' ')
1097             buf.append(cast(char) b);
1098         else if (b == '\r')
1099             buf.append("\\r");
1100         else if (b == '\n')
1101             buf.append("\\n");
1102         else if (b == '\t')
1103             buf.append("\\t");
1104         else
1105             buf.append("\\x").append(ConverterUtils.toHexString(b));
1106     }
1107 
1108     /* ------------------------------------------------------------ */
1109 
1110     /**
1111      * Convert buffer to a Hex Summary string.
1112      *
1113      * @param buffer the buffer to generate a hex byte summary from
1114      * @return A string showing a summary of the content in hex
1115      */
1116     static string toHexSummary(ByteBuffer buffer) {
1117         if (buffer is null)
1118             return "null";
1119         StringBuilder buf = new StringBuilder();
1120 
1121         buf.append("b[").append(buffer.remaining()).append("]=");
1122         for (int i = buffer.position(); i < buffer.limit(); i++) {
1123             ConverterUtils.toHex(buffer.get(i), buf);
1124             if (i == buffer.position() + 24 && buffer.limit() > buffer.position() + 32) {
1125                 buf.append("...");
1126                 i = buffer.limit() - 8;
1127             }
1128         }
1129         return buf.toString();
1130     }
1131 
1132     /* ------------------------------------------------------------ */
1133 
1134     /**
1135      * Convert buffer to a Hex string.
1136      *
1137      * @param buffer the buffer to generate a hex byte summary from
1138      * @return A hex string
1139      */
1140     static string toHexString(ByteBuffer buffer) {
1141         if (buffer is null)
1142             return "null";
1143         return ConverterUtils.toHexString(toArray(buffer));
1144     }
1145 
1146     private enum int[] decDivisors = [
1147             1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1
1148         ];
1149 
1150     private enum int[] hexDivisors = [
1151             0x10000000, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, 0x10, 0x1
1152         ];
1153 
1154     private enum long[] decDivisorsL = [
1155             1000000000000000000L, 100000000000000000L, 10000000000000000L,
1156             1000000000000000L, 100000000000000L, 10000000000000L, 1000000000000L,
1157             100000000000L, 10000000000L, 1000000000L, 100000000L, 10000000L,
1158             1000000L, 100000L, 10000L, 1000L, 100L, 10L, 1L
1159         ];
1160 
1161     static void putCRLF(ByteBuffer buffer) {
1162         buffer.put(cast(byte) 13);
1163         buffer.put(cast(byte) 10);
1164     }
1165 
1166     static bool isPrefix(ByteBuffer prefix, ByteBuffer buffer) {
1167         if (prefix.remaining() > buffer.remaining())
1168             return false;
1169         int bi = buffer.position();
1170         for (int i = prefix.position(); i < prefix.limit(); i++)
1171             if (prefix.get(i) != buffer.get(bi++))
1172                 return false;
1173         return true;
1174     }
1175 
1176     static ByteBuffer ensureCapacity(ByteBuffer buffer, size_t capacity) {
1177         if (buffer is null)
1178             return allocate(capacity);
1179 
1180         if (buffer.capacity() >= capacity)
1181             return buffer;
1182 
1183         if (buffer.hasArray()) {
1184             byte[] b = buffer.array();
1185             size_t offset = buffer.arrayOffset();
1186             size_t remaining = b.length - offset;
1187             byte[] copy = new byte[capacity];
1188             assert(remaining <= capacity);
1189             // if(remaining  > capacity)
1190             //     copy[0.. capacity] = b[offset .. offset+capacity];
1191             // else
1192             copy[0 .. remaining] = b[offset .. $];
1193             return toBuffer(copy, buffer.position(), buffer.remaining());
1194         }
1195 
1196         throw new UnsupportedOperationException();
1197     }
1198 
1199     /**
1200      * The capacity modulo 1024 is 0
1201      *
1202      * @param capacity the buffer size
1203      * @return the buffer size that modulo 1024 is 0
1204      */
1205     static int normalizeBufferSize(int capacity) {
1206         int q = capacity >>> 10;
1207         int r = capacity & 1023;
1208         if (r != 0) {
1209             q++;
1210         }
1211         return q << 10;
1212     }
1213 
1214     static string toString(List!(ByteBuffer) list) {
1215         Appender!(byte[]) ot;
1216         try {
1217             foreach (ByteBuffer buffer; list) {
1218                 // auto array = buffer.array();
1219                 // int from = buffer.arrayOffset() + buffer.position();
1220                 // int to = from + buffer.remaining();
1221                 // ot.put(array[from .. to]);
1222                 // buffer.position = buffer.position() + buffer.remaining();
1223                 writeTo(buffer, ot);
1224             }
1225             return cast(string) ot.data;
1226         } catch (IOException e) {
1227             return null;
1228         }
1229     }
1230 
1231     // static List!(ByteBuffer) split(ByteBuffer buffer, int maxSize) {
1232     //     if (buffer.remaining() <= maxSize) {
1233     //         return Collections.singletonList(buffer.duplicate());
1234     //     } else {
1235     //         ByteBuffer tmpBuffer = buffer.duplicate();
1236     //         int num = (buffer.remaining() + maxSize - 1) / maxSize;
1237     //         List!(ByteBuffer) list = new ArrayList<>(num);
1238     //         for (int i = 1; i <= num; i++) {
1239     //             ByteBuffer b = ByteBuffer.allocate(maxSize);
1240     //             byte[] data = new byte[std.algorithm.min(maxSize, tmpBuffer.remaining())];
1241     //             tmpBuffer.get(data);
1242     //             b.put(data).flip();
1243     //             list.add(b);
1244     //         }
1245     //         return list;
1246     //     }
1247 
1248     // }
1249 
1250     static long remaining(ByteBuffer[] byteBuffers) {
1251         long count = 0;
1252         foreach (ByteBuffer byteBuffer; byteBuffers) {
1253             count += byteBuffer.remaining();
1254         }
1255         return count;
1256     }
1257 
1258     static long remaining(Collection!ByteBuffer collection) {
1259         long count = 0;
1260         foreach (ByteBuffer byteBuffer; collection) {
1261             count += byteBuffer.remaining();
1262         }
1263         return count;
1264     }
1265 
1266     static byte[] toArray(List!(ByteBuffer) list) {
1267         Appender!(byte[]) ot;
1268         try {
1269             foreach (ByteBuffer buffer; list) {
1270                 // BufferUtils.writeTo(buf, out);
1271                 // auto array = buffer.array();
1272                 // int from = buffer.arrayOffset() + buffer.position();
1273                 // int to = from + buffer.remaining();
1274                 // ot.put(array[from .. to]);
1275                 // buffer.position = buffer.position() + buffer.remaining();
1276 
1277                 writeTo(buffer, ot);
1278             }
1279             return ot.data;
1280         } catch (IOException e) {
1281             return null;
1282         }
1283     }
1284 
1285     // static ByteBuffer toDirectBuffer(ByteBuffer buf) {
1286     //     if (buf.isDirect()) {
1287     //         return buf;
1288     //     } else {
1289     //         ByteBuffer directBuf = ByteBuffer.allocateDirect(buf.remaining());
1290     //         directBuf.put(buf.slice()).flip();
1291     //         return directBuf;
1292     //     }
1293     // }
1294 
1295     static ByteBuffer toHeapBuffer(ByteBuffer buf) {
1296         if (buf.isDirect()) {
1297             ByteBuffer heapBuffer = allocate(buf.remaining());
1298             heapBuffer.put(buf.slice()).flip();
1299             return heapBuffer;
1300         } else {
1301             return buf;
1302         }
1303     }
1304 
1305     static ByteBuffer clone(ByteBuffer buf) {
1306         byte[] bytes = buf.getRemaining();
1307         ByteBuffer heapBuffer = new HeapByteBuffer(bytes.dup, 0, cast(int)bytes.length);
1308         return heapBuffer;
1309 
1310     }
1311 }