1 /* 2 * hunt-proton: AMQP Protocol 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.proton.codec.CompositeReadableBuffer; 13 14 import hunt.io.ByteBuffer; 15 //import java.nio.CharBuffer; 16 //import java.nio.InvalidMarkException; 17 //import java.nio.charset.CharacterCodingException; 18 //import java.nio.charset.CharsetDecoder; 19 //import java.nio.charset.CoderResult; 20 //import java.nio.charset.StandardCharsets; 21 import hunt.collection.ArrayList; 22 import hunt.collection.Collections; 23 import hunt.collection.List; 24 import hunt.Exceptions; 25 import hunt.proton.codec.ReadableBuffer; 26 import hunt.proton.codec.WritableBuffer; 27 import hunt.io.BufferUtils; 28 import std.algorithm; 29 import std.math; 30 31 import hunt.Float; 32 import hunt.Double; 33 import hunt.Byte; 34 import hunt.String; 35 import std.concurrency : initOnce; 36 import std.conv; 37 /** 38 * ReadableBuffer implementation whose content is made up of one or more 39 * byte arrays. 40 */ 41 class CompositeReadableBuffer : ReadableBuffer { 42 43 // private static List!(byte[]) EMPTY_LIST = new ArrayList!(byte[])(); 44 // private static ByteBuffer EMPTY_BUFFER = BufferUtils.toBuffer(new byte[0]); 45 // private static CompositeReadableBuffer EMPTY_SLICE = new CompositeReadableBuffer(false); 46 private static int UNSET_MARK = -1; 47 48 private static int SHORT_BYTES = 2; 49 private static int INT_BYTES = 4; 50 private static int LONG_BYTES = 8; 51 52 private ArrayList!(byte[]) contents; 53 54 // Track active array and our offset into it. 55 private int currentArrayIndex = -1; 56 private byte[] currentArray; 57 private int currentOffset; 58 59 // State global to the buffer. 60 private int _position; 61 private int _limit; 62 private int _capacity; 63 private int _mark = -1; 64 private bool compactable = true; 65 66 67 static List!(byte[]) EMPTY_LIST() { 68 __gshared List!(byte[]) inst; 69 return initOnce!inst(new ArrayList!(byte[])()); 70 } 71 72 static ByteBuffer EMPTY_BUFFER() { 73 __gshared ByteBuffer inst; 74 return initOnce!inst(BufferUtils.toBuffer(new byte[0])); 75 } 76 77 78 static CompositeReadableBuffer EMPTY_SLICE() { 79 __gshared CompositeReadableBuffer inst; 80 return initOnce!inst(new CompositeReadableBuffer(false)); 81 } 82 83 /** 84 * Creates a default empty composite buffer 85 */ 86 this() { 87 } 88 89 this(byte[] array, int offset) { 90 this.currentArray = array; 91 this.currentOffset = offset; 92 if(array != null) { 93 this._capacity = cast(int)array.length; 94 } 95 this._limit = _capacity; 96 } 97 98 this(bool compactable) { 99 this.compactable = compactable; 100 } 101 102 public List!(byte[]) getArrays() { 103 return contents is null ? EMPTY_LIST :contents; 104 } 105 106 public int getCurrentIndex() { 107 return currentArrayIndex; 108 } 109 110 /** 111 * Gets the current position index in the current backing array, which represents the current buffer position. 112 * 113 * This value includes any buffer position movement, and resets when moving across array segments, so it only 114 * gives the starting offset for the first array if the buffer position is 0. 115 * 116 * Value may be out of array bounds if the the buffer currently has no content remaining. 117 * 118 * @return the position index in the current array representing the current buffer position. 119 */ 120 public int getCurrentArrayPosition() { 121 return currentOffset; 122 } 123 124 override 125 public bool hasArray() { 126 return currentArray != null && (contents is null || contents.size() == 1); 127 } 128 129 public int capacity() { 130 return this._capacity; 131 } 132 133 override 134 public byte[] array() { 135 if (hasArray()) { 136 return currentArray; 137 } 138 139 throw new UnsupportedOperationException("Buffer not backed by a single array"); 140 } 141 142 override 143 public int arrayOffset() { 144 if (hasArray()) { 145 return currentOffset - _position; 146 } 147 148 throw new UnsupportedOperationException("Buffer not backed by a single array"); 149 } 150 151 override 152 public byte get() { 153 if (_position == _limit) { 154 throw new BufferUnderflowException(); 155 } 156 157 byte result = currentArray[currentOffset++]; 158 _position++; 159 maybeMoveToNextArray(); 160 161 return result; 162 } 163 164 override 165 public byte get(int index) { 166 if (index < 0 || index >= _limit) { 167 throw new IndexOutOfBoundsException("The given index is not valid: " ~ to!string(index)); 168 } 169 170 byte result = 0; 171 172 if (index == _position) { 173 result = currentArray[currentOffset]; 174 } else if (index < _position) { 175 result = getBackwards(index); 176 } else { 177 result = getForward(index); 178 } 179 180 return result; 181 } 182 183 private byte getForward(int index) { 184 byte result = 0; 185 186 int currentArrayIndex = this.currentArrayIndex; 187 int currentOffset = this.currentOffset; 188 byte[] currentArray = this.currentArray; 189 190 for (int amount = index - _position; amount >= 0;) { 191 if (amount < currentArray.length - currentOffset) { 192 result = currentArray[currentOffset + amount]; 193 break; 194 } else { 195 amount -= currentArray.length - currentOffset; 196 currentArray = contents.get(++currentArrayIndex); 197 currentOffset = 0; 198 } 199 } 200 201 return result; 202 } 203 204 private byte getBackwards(int index) { 205 byte result = 0; 206 207 int currentArrayIndex = this.currentArrayIndex; 208 int currentOffset = this.currentOffset; 209 byte[] currentArray = this.currentArray; 210 211 for (int amount = _position - index; amount >= 0;) { 212 if ((currentOffset - amount) >= 0) { 213 result = currentArray[currentOffset - amount]; 214 break; 215 } else { 216 amount -= currentOffset; 217 currentArray = contents.get(--currentArrayIndex); 218 currentOffset = cast(int)currentArray.length; 219 } 220 } 221 222 return result; 223 } 224 225 override 226 public int getInt() { 227 if (remaining() < INT_BYTES) { 228 throw new BufferUnderflowException(); 229 } 230 231 int result = 0; 232 233 if (currentArray.length - currentOffset >= 4) { 234 result = cast(int)(currentArray[currentOffset++] & 0xFF) << 24 | 235 cast(int)(currentArray[currentOffset++] & 0xFF) << 16 | 236 cast(int)(currentArray[currentOffset++] & 0xFF) << 8 | 237 cast(int)(currentArray[currentOffset++] & 0xFF) << 0; 238 maybeMoveToNextArray(); 239 } else { 240 for (int i = INT_BYTES - 1; i >= 0; --i) { 241 result |= cast(int)(currentArray[currentOffset++] & 0xFF) << (i * Byte.SIZE); 242 maybeMoveToNextArray(); 243 } 244 } 245 246 _position += 4; 247 248 return result; 249 } 250 251 override 252 public long getLong() { 253 if (remaining() < LONG_BYTES) { 254 throw new BufferUnderflowException(); 255 } 256 257 long result = 0; 258 259 if (currentArray.length - currentOffset >= 8) { 260 result = cast(long)(currentArray[currentOffset++] & 0xFF) << 56 | 261 cast(long)(currentArray[currentOffset++] & 0xFF) << 48 | 262 cast(long)(currentArray[currentOffset++] & 0xFF) << 40 | 263 cast(long)(currentArray[currentOffset++] & 0xFF) << 32 | 264 cast(long)(currentArray[currentOffset++] & 0xFF) << 24 | 265 cast(long)(currentArray[currentOffset++] & 0xFF) << 16 | 266 cast(long)(currentArray[currentOffset++] & 0xFF) << 8 | 267 cast(long)(currentArray[currentOffset++] & 0xFF) << 0; 268 maybeMoveToNextArray(); 269 } else { 270 for (int i = LONG_BYTES - 1; i >= 0; --i) { 271 result |= cast(long)(currentArray[currentOffset++] & 0xFF) << (i * Byte.SIZE); 272 maybeMoveToNextArray(); 273 } 274 } 275 276 _position += 8; 277 278 return result; 279 } 280 281 override 282 public short getShort() { 283 if (remaining() < SHORT_BYTES) { 284 throw new BufferUnderflowException(); 285 } 286 287 short result = 0; 288 289 for (int i = SHORT_BYTES - 1; i >= 0; --i) { 290 result |= (currentArray[currentOffset++] & 0xFF) << (i * Byte.SIZE); 291 maybeMoveToNextArray(); 292 } 293 294 _position += 2; 295 296 return result; 297 } 298 299 override 300 public float getFloat() { 301 return Float.intBitsToFloat(getInt()); 302 } 303 304 override 305 public double getDouble() { 306 return Double.longBitsToDouble(getLong()); 307 } 308 309 override 310 public CompositeReadableBuffer get(byte[] data) { 311 return get(data, 0, cast(int)data.length); 312 } 313 314 override 315 public CompositeReadableBuffer get(byte[] data, int offset, int length) { 316 validateReadTarget(cast(int)data.length, offset, length); 317 318 if (length > remaining()) { 319 throw new BufferUnderflowException(); 320 } 321 322 int copied = 0; 323 while (length > 0) { 324 int chunk = min((currentArray.length - currentOffset), length); 325 // System.arraycopy(currentArray, currentOffset, data, offset + copied, chunk); 326 327 int end = offset + copied + chunk; 328 int start = offset + copied; 329 330 data[start .. end] = currentArray[currentOffset .. currentOffset + chunk]; 331 332 currentOffset += chunk; 333 length -= chunk; 334 copied += chunk; 335 336 maybeMoveToNextArray(); 337 } 338 339 _position += copied; 340 341 return this; 342 } 343 344 override 345 public CompositeReadableBuffer get(WritableBuffer target) { 346 int length = min(target.remaining(), remaining()); 347 348 do { 349 int chunk = min((currentArray.length - currentOffset), length); 350 351 if (chunk == 0) { 352 break; // This buffer is out of data 353 } 354 355 target.put(currentArray, currentOffset, chunk); 356 357 currentOffset += chunk; 358 _position += chunk; 359 length -= chunk; 360 361 maybeMoveToNextArray(); 362 } while (length > 0); 363 364 return this; 365 } 366 367 368 public CompositeReadableBuffer position(int pos) { 369 if (pos < 0 || pos > _limit) { 370 throw new IllegalArgumentException("position must be non-negative and no greater than the limit"); 371 } 372 373 int moveBy = pos - this._position; 374 if (moveBy >= 0) { 375 moveForward(moveBy); 376 } else { 377 moveBackwards(abs(moveBy)); 378 } 379 380 this._position = pos; 381 382 if (_mark > pos) { 383 _mark = UNSET_MARK; 384 } 385 386 return this; 387 } 388 389 private void moveForward(int moveBy) { 390 while (moveBy > 0) { 391 if (moveBy < currentArray.length - currentOffset) { 392 currentOffset += moveBy; 393 break; 394 } else { 395 moveBy -= currentArray.length - currentOffset; 396 if (currentArrayIndex != -1 && currentArrayIndex < contents.size() - 1) { 397 currentArray = contents.get(++currentArrayIndex); 398 currentOffset = 0; 399 } else { 400 currentOffset = cast(int)currentArray.length; 401 } 402 } 403 } 404 } 405 406 private void moveBackwards(int moveBy) { 407 while (moveBy > 0) { 408 if ((currentOffset - moveBy) >= 0) { 409 currentOffset -= moveBy; 410 break; 411 } else { 412 moveBy -= currentOffset; 413 currentArray = contents.get(--currentArrayIndex); 414 currentOffset = cast(int)currentArray.length; 415 } 416 } 417 } 418 419 override 420 public int position() { 421 return _position; 422 } 423 424 override 425 public CompositeReadableBuffer slice() { 426 int newCapacity = limit() - position(); 427 428 CompositeReadableBuffer result; 429 430 if (newCapacity == 0) { 431 result = EMPTY_SLICE; 432 } else { 433 result = new CompositeReadableBuffer(currentArray, currentOffset); 434 result.contents = contents; 435 result.currentArrayIndex = currentArrayIndex; 436 result._capacity = newCapacity; 437 result._limit = newCapacity; 438 result._position = 0; 439 result.compactable = false; 440 } 441 442 return result; 443 } 444 445 override 446 public CompositeReadableBuffer flip() { 447 _limit = _position; 448 position(0); // Move by index to avoid corrupting a slice. 449 _mark = UNSET_MARK; 450 451 return this; 452 } 453 454 override 455 public CompositeReadableBuffer limit(int limit) { 456 if (limit < 0 || limit > _capacity) { 457 throw new IllegalArgumentException("limit must be non-negative and no greater than the capacity"); 458 } 459 460 if (_mark > limit) { 461 _mark = UNSET_MARK; 462 } 463 464 if (_position > limit) { 465 position(limit); 466 } 467 468 this._limit = limit; 469 470 return this; 471 } 472 473 override 474 public int limit() { 475 return _limit; 476 } 477 478 override 479 public CompositeReadableBuffer mark() { 480 this._mark = _position; 481 return this; 482 } 483 484 override 485 public CompositeReadableBuffer reset() { 486 if (_mark < 0) { 487 throw new InvalidMarkException(); 488 } 489 490 position(_mark); 491 492 return this; 493 } 494 495 override 496 public CompositeReadableBuffer rewind() { 497 return position(0); 498 } 499 500 override 501 public CompositeReadableBuffer clear() { 502 _mark = UNSET_MARK; 503 _limit = _capacity; 504 505 return position(0); 506 } 507 508 override 509 public int remaining() { 510 return _limit - _position; 511 } 512 513 override 514 public bool hasRemaining() { 515 return remaining() > 0; 516 } 517 518 override 519 public CompositeReadableBuffer duplicate() { 520 CompositeReadableBuffer duplicated = 521 new CompositeReadableBuffer(currentArray, currentOffset); 522 523 if (contents !is null) { 524 duplicated.contents = new ArrayList!(byte[])(contents); 525 } 526 527 duplicated._capacity = _capacity; 528 duplicated.currentArrayIndex = currentArrayIndex; 529 duplicated._limit = _limit; 530 duplicated._position = _position; 531 duplicated._mark = _mark; 532 duplicated.compactable = compactable; // A slice duplicated should not allow compaction. 533 534 return duplicated; 535 } 536 537 override 538 public ByteBuffer byteBuffer() { 539 int viewSpan = limit() - position(); 540 541 ByteBuffer result; 542 543 if (viewSpan == 0) { 544 result = EMPTY_BUFFER; 545 } else if (viewSpan <= currentArray.length - currentOffset) { 546 result = BufferUtils.toBuffer(currentArray, currentOffset, viewSpan); 547 } else { 548 result = buildByteBuffer(viewSpan); 549 } 550 551 return result; 552 } 553 554 private ByteBuffer buildByteBuffer(int span) { 555 //byte[] compactedView = new byte[span]; 556 557 //byte[] compactedView = new byte[span]; 558 //int arrayIndex = currentArrayIndex; 559 // 560 //// Take whatever is left from the current array; 561 //System.arraycopy(currentArray, currentOffset, compactedView, 0, currentArray.length - currentOffset); 562 //int copied = currentArray.length - currentOffset; 563 // 564 //while (copied < span) { 565 // byte[] next = contents.get(++arrayIndex); 566 // final int length = Math.min(span - copied, next.length); 567 // System.arraycopy(next, 0, compactedView, copied, length); 568 // copied += length; 569 //} 570 571 572 573 byte[] compactedView = new byte[span]; 574 int arrayIndex = currentArrayIndex; 575 576 compactedView[0 .. (cast(int)currentArray.length - currentOffset)] = currentArray[currentOffset .. (currentOffset+cast(int)currentArray.length - currentOffset)]; 577 int copied = cast(int)currentArray.length - currentOffset; 578 579 while (copied < span) { 580 byte[] next = contents.get(++arrayIndex); 581 int length = min(span - copied, cast(int)next.length); 582 // System.arraycopy(next, 0, compactedView, copied, length); 583 compactedView[copied .. copied+length] = next[0 ..length ]; 584 copied += length; 585 } 586 587 // compactedView ~= currentArray[currentOffset .. $]; 588 // Take whatever is left from the current array; 589 //System.arraycopy(currentArray, currentOffset, compactedView, 0, currentArray.length - currentOffset); 590 //int copied = currentArray.length - currentOffset; 591 592 //for (;arrayIndex < contents.size();) 593 //{ 594 // byte[] next = contents.get(++arrayIndex); 595 // compactedView ~= next; 596 //} 597 598 //while (copied < span) { 599 // byte[] next = contents.get(++arrayIndex); 600 // int length = min(span - copied, next.length); 601 // System.arraycopy(next, 0, compactedView, copied, length); 602 // copied += length; 603 //} 604 605 return BufferUtils.toBuffer(compactedView); 606 } 607 608 override 609 public string readUTF8(){ 610 return readString(); 611 } 612 613 public string readString() { 614 if (!hasRemaining()) { 615 return ( ""); 616 } 617 618 if (hasArray()) { 619 return cast(string)BufferUtils.toBuffer(currentArray, currentOffset, remaining()).getRemaining(); 620 } else { 621 return readStringFromComponents(); 622 } 623 // CharBuffer decoded = null; 624 625 // if (hasArray()) { 626 //return new String( cast(string)currentArray[currentOffset .. currentOffset+remaining()]); 627 // BufferUtils.toBuffer(currentArray, currentOffset, remaining()); 628 // decoded = decoder.decode(ByteBuffer.wrap(currentArray, currentOffset, remaining())); 629 // } 630 //else { 631 // decoded = readStringFromComponents(decoder); 632 //} 633 634 // return decoded.toString(); 635 } 636 637 private string readStringFromComponents(){ 638 int size = cast(int)(remaining() * 1); 639 // CharBuffer decoded = CharBuffer.allocate(size); 640 641 int arrayIndex = currentArrayIndex; 642 int viewSpan = limit() - position(); //23 643 int processed = min(currentArray.length - currentOffset, viewSpan); //11 644 byte[] wrapper = BufferUtils.toBuffer(currentArray, currentOffset, processed).getRemaining(); 645 while (processed != viewSpan && arrayIndex <= contents.size()) 646 { 647 byte[] next = contents.get(++arrayIndex); 648 int wrapSize = min(next.length, viewSpan - processed); 649 byte [] tmp = BufferUtils.toBuffer(next,0,wrapSize).getRemaining(); 650 wrapper ~= tmp; 651 processed += wrapSize; 652 } 653 654 return (cast(string)wrapper); 655 //CoderResult step = CoderResult.OVERFLOW; 656 // 657 //do { 658 // bool endOfInput = processed == viewSpan; 659 // step = decoder.decode(wrapper, decoded, endOfInput); 660 // if (step.isUnderflow() && endOfInput) { 661 // step = decoder.flush(decoded); 662 // break; 663 // } 664 // 665 // if (step.isOverflow()) { 666 // size = 2 * size + 1; 667 // CharBuffer upsized = CharBuffer.allocate(size); 668 // decoded.flip(); 669 // upsized.put(decoded); 670 // decoded = upsized; 671 // continue; 672 // } 673 // 674 // byte[] next = contents.get(++arrayIndex); 675 // int wrapSize = Math.min(next.length, viewSpan - processed); 676 // wrapper = ByteBuffer.wrap(next, 0, wrapSize); 677 // processed += wrapSize; 678 //} while (!step.isError()); 679 // 680 //if (step.isError()) { 681 // step.throwException(); 682 //} 683 // 684 //return (CharBuffer) decoded.flip(); 685 } 686 687 /** 688 * Compact the buffer dropping arrays that have been consumed by previous 689 * reads from this Composite buffer. The limit is reset to the new _capacity 690 */ 691 override 692 public CompositeReadableBuffer reclaimRead() { 693 if (!compactable || (currentArray is null && contents is null)) { 694 return this; 695 } 696 697 int totalCompaction = 0; 698 int totalRemovals = 0; 699 700 for (; totalRemovals < currentArrayIndex; ++totalRemovals) { 701 byte[] element = contents.removeAt(0); 702 totalCompaction += element.length; 703 } 704 705 currentArrayIndex -= totalRemovals; 706 707 if (currentArray.length == currentOffset) { 708 totalCompaction += currentArray.length; 709 710 // If we are sitting on the end of the data (length == offest) then 711 // we are also at the last element in the ArrayList if one is currently 712 // in use, so remove the data and release the list. 713 if (currentArrayIndex == 0) { 714 contents.clear(); 715 contents = null; 716 } 717 718 currentArray = null; 719 currentArrayIndex = -1; 720 currentOffset = 0; 721 } 722 723 _position -= totalCompaction; 724 _limit = _capacity -= totalCompaction; 725 726 if (_mark != UNSET_MARK) { 727 _mark -= totalCompaction; 728 } 729 730 return this; 731 } 732 733 /** 734 * Adds the given array into the composite buffer at the end. 735 * <p> 736 * The appended array is not copied so changes to the source array are visible in this 737 * buffer and vice versa. If this composite was empty than it would return true for the 738 * {@link #hasArray()} method until another array is appended. 739 * <p> 740 * Calling this method resets the limit to the new _capacity. 741 * 742 * @param array 743 * The array to add to this composite buffer. 744 * 745 * @throws IllegalArgumentException if the array is null or zero size. 746 * @throws IllegalStateException if the buffer does not allow appends. 747 * 748 * @return a reference to this {@link CompositeReadableBuffer}. 749 */ 750 public CompositeReadableBuffer append(byte[] array) { 751 validateAppendable(); 752 753 if (array is null || array.length == 0) { 754 throw new IllegalArgumentException("Array must not be empty or null"); 755 } 756 757 if (currentArray is null) { 758 currentArray = array; 759 currentOffset = 0; 760 } else if (contents is null) { 761 contents = new ArrayList!(byte[]); 762 contents.add(currentArray); 763 contents.add(array); 764 currentArrayIndex = 0; 765 // If we exhausted the array previously then it should move to the new one now. 766 maybeMoveToNextArray(); 767 } else { 768 contents.add(array); 769 // If we exhausted the list previously then it didn't move onward at the time, so it should now. 770 maybeMoveToNextArray(); 771 } 772 773 _capacity += array.length; 774 _limit = _capacity; 775 776 return this; 777 } 778 779 private void validateAppendable() { 780 if (!compactable) { 781 throw new IllegalStateException(); 782 } 783 } 784 785 private void validateBuffer(ReadableBuffer buffer) { 786 if (buffer is null) { 787 throw new IllegalArgumentException("A non-null buffer must be provided"); 788 } 789 790 if (!buffer.hasRemaining()) { 791 throw new IllegalArgumentException("Buffer has no remaining content to append"); 792 } 793 } 794 795 /** 796 * Adds the given composite buffer contents (from current position, up to the limit) into this 797 * composite buffer at the end. The source buffer position will be set to its limit. 798 * <p> 799 * The appended buffer contents are not copied wherever possible, so changes to the source 800 * arrays are typically visible in this buffer and vice versa. Exceptions include where the 801 * source buffer position is not located at the start of its current backing array, or where the 802 * given buffer has a limit that doesn't encompass all of the last array used, and 803 * so the remainder of that arrays contents must be copied first to append here. 804 * <p> 805 * Calling this method resets the limit to the new _capacity. 806 * 807 * @param buffer 808 * the buffer with contents to append into this composite buffer. 809 * 810 * @throws IllegalArgumentException if the given buffer is null or has zero remainder. 811 * @throws IllegalStateException if the buffer does not allow appends. 812 * 813 * @return a reference to this {@link CompositeReadableBuffer}. 814 */ 815 public CompositeReadableBuffer append(CompositeReadableBuffer buffer) { 816 validateAppendable(); 817 validateBuffer(buffer); 818 819 byte[] chunk; 820 do { 821 int bufferRemaining = buffer.remaining(); 822 int arrayRemaining = cast(int)(buffer.currentArray.length) - buffer.currentOffset; 823 if (buffer.currentOffset > 0 || bufferRemaining < arrayRemaining) { 824 int length = min(arrayRemaining, bufferRemaining); 825 // chunk = new byte[length]; 826 // System.arraycopy(buffer.currentArray, buffer.currentOffset, chunk, 0, length); 827 int endindex = buffer.currentOffset + length; 828 chunk = buffer.currentArray[buffer.currentOffset .. endindex].dup; 829 } else { 830 chunk = buffer.currentArray; 831 } 832 833 append(chunk); 834 835 buffer.position(buffer.position() + cast(int)chunk.length); 836 } while (buffer.hasRemaining()); 837 838 return this; 839 } 840 841 /** 842 * Adds the given readable buffer contents (from current position, up to the limit) into this 843 * composite buffer at the end. The source buffer position will be set to its limit. 844 * <p> 845 * The appended buffer contents are not copied wherever possible, so changes to the source 846 * arrays are typically visible in this buffer and vice versa. Exceptions are where the 847 * source buffer is not backed by an array, or where the source buffer position is not 848 * located at the start of its backing array, and so the remainder of the contents must 849 * be copied first to append here. 850 * <p> 851 * Calling this method resets the limit to the new _capacity. 852 * 853 * @param buffer 854 * the buffer with contents to append into this composite buffer. 855 * 856 * @throws IllegalArgumentException if the given buffer is null or has zero remainder. 857 * @throws IllegalStateException if the buffer does not allow appends. 858 * 859 * @return a reference to this {@link CompositeReadableBuffer}. 860 */ 861 public CompositeReadableBuffer append(ReadableBuffer buffer) { 862 CompositeReadableBuffer cBuffer = cast(CompositeReadableBuffer)buffer; 863 if(cBuffer !is null) { 864 append(cBuffer); 865 } else { 866 validateAppendable(); 867 validateBuffer(buffer); 868 869 if (buffer.hasArray()) { 870 871 byte[] chunk = buffer.array(); 872 873 int bufferRemaining = buffer.remaining(); 874 if (buffer.arrayOffset() > 0 || bufferRemaining < chunk.length) { 875 int endindex = buffer.arrayOffset() + bufferRemaining; 876 chunk = buffer.array[buffer.arrayOffset() .. endindex].dup; 877 // System.arraycopy(buffer.array(), buffer.arrayOffset(), chunk, 0, bufferRemaining); 878 } 879 880 append(chunk); 881 882 buffer.position(buffer.position() + cast(int)chunk.length); 883 } else { 884 byte[] chunk = new byte[buffer.remaining()]; 885 buffer.get(chunk); 886 887 append(chunk); 888 } 889 } 890 891 return this; 892 } 893 894 //override 895 public int hashCode() { 896 int hash = 1; 897 int remaining = remaining(); 898 899 if (currentArrayIndex < 0 || remaining <= currentArray.length - currentOffset) { 900 while (remaining > 0) { 901 hash = 31 * hash + currentArray[currentOffset + --remaining]; 902 } 903 } else { 904 hash = hashCodeFromComponents(); 905 } 906 907 return hash; 908 } 909 910 private int hashCodeFromComponents() { 911 int hash = 1; 912 byte[] array = currentArray; 913 int arrayOffset = currentOffset; 914 int arraysIndex = currentArrayIndex; 915 916 // Run to the the array and offset where we want to start the hash from 917 int remaining = remaining(); 918 for (int moveBy = remaining; moveBy > 0; ) { 919 if (moveBy <= array.length - arrayOffset) { 920 arrayOffset += moveBy; 921 break; 922 } else { 923 moveBy -= array.length - arrayOffset; 924 array = contents.get(++arraysIndex); 925 arrayOffset = 0; 926 } 927 } 928 929 // Now run backwards through the arrays to match what ByteBuffer would produce 930 for (int moveBy = remaining; moveBy > 0; moveBy--) { 931 hash = 31 * hash + array[--arrayOffset]; 932 if (arrayOffset == 0 && arraysIndex > 0) { 933 array = contents.get(--arraysIndex); 934 arrayOffset = cast(int)array.length; 935 } 936 } 937 938 return hash; 939 } 940 941 942 override int opCmp(ReadableBuffer o) 943 { 944 return this.capacity() - o.capacity(); 945 } 946 947 948 949 override bool opEquals(Object other) 950 { 951 if (this is other) { 952 return true; 953 } 954 955 ReadableBuffer buffer = cast(ReadableBuffer) other; 956 if ( buffer is null ) { 957 return false; 958 } 959 960 961 int remaining = remaining(); 962 if (remaining != buffer.remaining()) { 963 return false; 964 } 965 966 if (remaining == 0) { 967 // No content to compare, and we already checked 'remaining' is equal. Protects from NPE below. 968 return true; 969 } 970 971 if (hasArray() || remaining <= currentArray.length - currentOffset) { 972 // Either there is only one array, or the span to compare is within a single chunk of this buffer, 973 // allowing the compare to directly access the underlying array instead of using slower get methods. 974 return equals(currentArray, currentOffset, remaining, buffer); 975 } else { 976 return equals(this, buffer); 977 } 978 } 979 980 private static bool equals(byte[] buffer, int start, int length, ReadableBuffer other) { 981 int position = other.position(); 982 for (int i = 0; i < length; i++) { 983 if (buffer[start + i] != other.get(position + i)) { 984 return false; 985 } 986 } 987 return true; 988 } 989 990 private static bool equals(ReadableBuffer buffer, ReadableBuffer other) { 991 int origPos = buffer.position(); 992 try { 993 for (int i = other.position(); buffer.hasRemaining(); i++) { 994 if (!equals(buffer.get(), other.get(i))) { 995 return false; 996 } 997 } 998 return true; 999 } finally { 1000 buffer.position(origPos); 1001 } 1002 } 1003 1004 //override 1005 //public String toString() { 1006 // StringBuffer builder = new StringBuffer(); 1007 // builder.append("CompositeReadableBuffer"); 1008 // builder.append("{ pos="); 1009 // builder.append(position()); 1010 // builder.append(" limit="); 1011 // builder.append(limit()); 1012 // builder.append(" capacity="); 1013 // builder.append(capacity()); 1014 // builder.append(" }"); 1015 // 1016 // return builder.toString(); 1017 //} 1018 1019 private static bool equals(byte x, byte y) { 1020 return x == y; 1021 } 1022 1023 private void maybeMoveToNextArray() { 1024 if (currentArray.length == currentOffset) { 1025 if (currentArrayIndex >= 0 && currentArrayIndex < (contents.size() - 1)) { 1026 currentArray = contents.get(++currentArrayIndex); 1027 currentOffset = 0; 1028 } 1029 } 1030 } 1031 1032 private static void validateReadTarget(int destSize, int offset, int length) { 1033 if ((offset | length) < 0) { 1034 throw new IndexOutOfBoundsException("offset and legnth must be non-negative"); 1035 } 1036 1037 if ((cast(long) offset + cast(long) length) > destSize) { 1038 throw new IndexOutOfBoundsException("target is to small for specified read size"); 1039 } 1040 } 1041 }