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