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