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 }