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