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 }