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 }