/
ByteList.java
1411 lines (1264 loc) · 46.4 KB
/
ByteList.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2007-2010 JRuby Community
* Copyright (C) 2007 Charles O Nutter <headius@headius.com>
* Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
* Copyright (C) 2007 Ola Bini <ola@ologix.com>
* Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.jcodings.Encoding;
import org.jcodings.ascii.AsciiTables;
import org.jcodings.specific.ASCIIEncoding;
/**
* ByteList is simple a collection of bytes in the same way a Java String is a collection
* of characters. However, its API resembles StringBuffer/StringBuilder more than String
* because it is a mutable object.
*/
@SuppressWarnings("deprecation")
public final class ByteList implements Comparable, CharSequence, Serializable {
private static final long serialVersionUID = -1286166947275543731L;
public static final byte[] NULL_ARRAY = new byte[0];
public static final ByteList EMPTY_BYTELIST = new ByteList(0);
@Deprecated
public byte[] bytes;
@Deprecated
public int begin;
@Deprecated
public int realSize;
@Deprecated
public Encoding encoding = ASCIIEncoding.INSTANCE;
int hash;
String stringValue;
private static final int DEFAULT_SIZE = 4;
/**
* Creates a new instance of ByteList
*/
public ByteList() {
this(DEFAULT_SIZE);
}
/**
* Creates a new instance of Bytelist with a pre-allocated size. If you know the size ahead
* of time this saves additional array allocations to grow the bytelist to the proper size.
*
* @param size to preallocate the bytelist to
*/
public ByteList(int size) {
bytes = new byte[size];
realSize = 0;
}
/**
* Create a new instance of ByteList with the bytes supplied using the specified encoding.
*
* Important: bytes is used as the initial backing store for the bytelist. Over time as the
* bytelist is mutated this backing store may be replaced with a new one to hold the additional
* bytes. If you pass in bytes and then modify the contents of the original bytes, then those
* changes will get reflected.
*
* @param bytes to use
* @param encoding
*/
// TODO: Deprecate and replace with a static method which implies the caveats of this constructor.
public ByteList(byte[] bytes, Encoding encoding) {
this.bytes = bytes;
this.realSize = bytes.length;
this.encoding = safeEncoding(encoding);
}
/**
* Create a new instance of ByteList with the contents of wrap. This constructor will make
* a copy of bytes passed
*
* @param wrap the initial bytes for this ByteList
*/
public ByteList(byte[] wrap) {
this(wrap, true);
}
/**
* Create a new instance of ByteList with the contents of wrap. If copy is true then it will
* array copy the contents. Otherwise it will use the byte array passed in as its initial
* backing store.
*
* @param wrap the initial bytes for this ByteList
* @param copy whether to arraycopy wrap for the backing store or not
*/
public ByteList(byte[] wrap, boolean copy) {
this(wrap, ASCIIEncoding.INSTANCE, copy);
}
/**
* Create a new instance of ByteList with the contents of wrap. If copy is true then it will
* array copy the contents. Otherwise it will use the byte array passed in as its initial
* backing store.
*
* @param wrap the initial bytes for this ByteList
* @param encoding the encoding for the bytes
* @param copy whether to arraycopy wrap for the backing store or not
*/
public ByteList(byte[] wrap, Encoding encoding, boolean copy) {
assert wrap != null;
if (copy) {
this.bytes = (byte[])wrap.clone();
} else {
this.bytes = wrap;
}
this.realSize = wrap.length;
this.encoding = safeEncoding(encoding);
}
/**
* Create a new instance of byte list with the same contents as the passed in ByteList wrap.
* Note that this does array copy the data for the new objects initial backing store.
*
* @param wrap is contents for new ByteList
*/
public ByteList(ByteList wrap) {
this(wrap.bytes, wrap.begin, wrap.realSize, wrap.encoding, true);
}
/**
* Create a new instance of ByteList with the same contents as the passed in ByteList wrap.
* The copy parameter gives you control over whether you want this new ByteList to share
* the same byte array for its backing store.
*
* ****IMPORTANT NOTES*****
* copy is currently ignored and always assumed false. This constructor should just go away
* so it has been marked as deprecated.
*
* @param wrap
* @param copy
*
* Deprecated to coincide with JRuby 1.5 (not used by anything we can find luckily)
*/
@Deprecated
public ByteList(ByteList wrap, boolean copy) {
this(wrap.bytes, wrap.begin, wrap.realSize, wrap.encoding, false);
}
/**
* Create a new instance of ByteList using wrap as a backing store where index is the first
* index in the byte array where the data starts and len indicates how long the data portion
* of the bytelist is. wrap will be array copied in this constructor.
*
* @param wrap the bytes to use
* @param index where in the bytes the data starts
* @param len how long the data is in the wrap array
*/
public ByteList(byte[] wrap, int index, int len) {
this(wrap, index, len, true);
}
/**
* Create a new instance of ByteList using wrap as a backing store where index is the first
* index in the byte array where the data starts and len indicates how long the data portion
* of the bytelist is. wrap will be array copied if copy is true OR if index != 0.
*
* @param wrap the bytes to use
* @param index where in the bytes the data starts
* @param len how long the data is in the wrap array
* @param copy if true array copy wrap. otherwise use as backing store
*/
// FIXME: Fix the index != 0 not honoring copy and separate out into a different caller. JRuby.next would be the right time for this.
public ByteList(byte[] wrap, int index, int len, boolean copy) {
this(wrap, index, len, ASCIIEncoding.INSTANCE, copy);
}
/**
* Create a new instance of ByteList using wrap as a backing store where index is the first
* index in the byte array where the data starts and len indicates how long the data portion
* of the bytelist is. wrap will be array copied if copy is true OR if index != 0.
*
* @param wrap the bytes to use
* @param index where in the bytes the data starts
* @param len how long the data is in the wrap array
* @param copy if true array copy wrap. otherwise use as backing store
*/
public ByteList(byte[] wrap, int index, int len, Encoding encoding, boolean copy) {
assert wrap != null : "'wrap' must not be null";
assert index >= 0 && index <= wrap.length : "'index' is not without bounds of 'wrap' array";
assert wrap.length >= index + len : "'index' + 'len' is longer than the 'wrap' array";
if (copy) {
bytes = new byte[len];
System.arraycopy(wrap, index, bytes, 0, len);
} else {
begin = index;
bytes = wrap;
}
realSize = len;
this.encoding = safeEncoding(encoding);
}
/**
* Create a new instance of ByteList using wrap as a backing store where index is the first
* index in the byte array where the data starts and len indicates how long the data portion
* of the bytelist is. wrap's byte array will be array copied for initial backing store.
*
* @param wrap the bytes to use
* @param index where in the bytes the data starts
* @param len how long the data is in the wrap array
*/
public ByteList(ByteList wrap, int index, int len) {
this(wrap.bytes, wrap.begin + index, len);
encoding = wrap.encoding;
}
/**
* Delete len bytes from start index. This does no bullet-proofing so it is your
* responsibility to ensure you do not run off the backing store array.
*
* @param start index to delete from
* @param len number of bytes to delete
*/
public void delete(int start, int len) {
assert start >= begin && start < realSize : "'start' is at invalid index";
assert len >= 0 : "'len' must be positive";
assert start + len <= begin + realSize : "too many bytes requested";
realSize -= len;
System.arraycopy(bytes, start + len, bytes, start, realSize);
invalidate();
}
/**
* Append the byte b up to len times onto the end of the current ByteList.
*
* @param b is byte to be appended
* @param len is number of times to repeat the append
*/
// FIXME: Innefficient impl since we know the len up front.
public void fill(int b, int len) {
for ( ; --len >= 0; ) {
append(b);
}
invalidate();
}
/**
* @see Object#clone()
*/
@Override
public Object clone() {
return dup();
}
/**
* creates a duplicate of this bytelist but only in the case of a stringValue and its resulting
* hash value. No other elements are duplicated.
*/
public ByteList dup() {
ByteList dup = dup(realSize);
dup.hash = hash;
dup.stringValue = stringValue;
return dup;
}
/**
* Create a new ByteList but do not array copy the byte backing store.
*
* @return a new ByteList with same backing store
*/
public ByteList shallowDup() {
ByteList dup = new ByteList(bytes, false);
dup.realSize = realSize;
dup.begin = begin;
dup.encoding = safeEncoding(encoding);
dup.hash = hash;
dup.stringValue = stringValue;
return dup;
}
/**
* @param length is the value of how big the buffer is going to be, not the actual length to copy
*
* It is used by RubyString.modify(int) to prevent COW pathological situations
* (namely to COW with having <code>length - realSize</code> bytes ahead)
*/
public ByteList dup(int length) {
ByteList dup = new ByteList(length);
dup.append(this.bytes, this.begin, this.realSize);
dup.encoding = safeEncoding(encoding);
return dup;
}
/**
* Ensure that the bytelist is at least length bytes long. Otherwise grow the backing store
* so that it is length bytes long
*
* @param length to use to make sure ByteList is long enough
*/
public void ensure(int length) {
if (begin + length > bytes.length) {
byte[] tmp = new byte[Math.min(Integer.MAX_VALUE, length + (length >>> 1))];
System.arraycopy(bytes, begin, tmp, 0, realSize);
bytes = tmp;
begin = 0;
invalidate();
}
}
/**
* Make a shared copy of this ByteList. This is used for COW'ing ByteLists, you typically
* want a piece of the same backing store to be shared across ByteBuffers, while those
* ByteLists will be pointing at different indexes and lengths of the same backing store.
*
* Note: that this does not update hash or stringValue.
*
* @param index new begin value for shared ByteBuffer
* @param len new length/realSize for chared
* @return
*/
public ByteList makeShared(int index, int len) {
ByteList shared = new ByteList(bytes, encoding);
shared.realSize = len;
shared.begin = begin + index;
return shared;
}
/**
* Change ByteBuffer to have a new begin that is +index positions past begin with a new length.
*
* @param index new value to add to begin
* @param len the new realSize/length value
*/
public void view(int index, int len) {
realSize = len;
begin = begin + index;
invalidate();
}
/**
* Array copy the byte backing store so that you can guarantee that no other objects are
* referencing this objects backing store.
*/
public void unshare() {
unshare(realSize);
}
/**
* Array copy the byte backing store so that you can guarantee that no other objects are
* referencing this objects backing store. This version on unshare allows a length to be
* specified which will copy length bytes from the old backing store.
*
* @param length is the value of how big the buffer is going to be, not the actual length to copy
*
* It is used by RubyString.modify(int) to prevent COW pathological situations
* (namely to COW with having <code>length - realSize</code> bytes ahead)
*/
public void unshare(int length) {
byte[] tmp = new byte[length];
System.arraycopy(bytes, begin, tmp, 0, Math.min(realSize, length));
bytes = tmp;
begin = 0;
}
/**
* Invalidate the hash and stringValue which may have been cached in this ByteList.
*/
public void invalidate() {
hash = 0;
stringValue = null;
}
/**
* Prepend a byte onto the front of this ByteList.
*
* @param b is the byte to be prepended
*/
public void prepend(byte b) {
grow(1);
System.arraycopy(bytes, begin + 0, bytes, begin + 1, realSize);
bytes[begin + 0] = b;
realSize++;
invalidate();
}
/**
* Append a single byte to the ByteList
*
* @param b the byte to be added
* @return this instance
*/
public ByteList append(byte b) {
grow(1);
bytes[begin + realSize] = b;
realSize++;
invalidate();
return this;
}
/**
* Append a single int to the ByteList
*
* @param b the int to be added
* @return this instance
*/
public ByteList append(int b) {
append((byte)b);
return this;
}
/**
* Append up to length bytes from InputStream to the ByteList. If no bytes are read from the
* stream then throw an IOException.
*
* @param input the stream to read bytes from
* @param length how many byte to try and read
* @return this instance
* @throws IOException when no bytes are read
*/
public ByteList append(InputStream input, int length) throws IOException {
grow(length);
int read = 0;
int n;
int start = begin + realSize;
while (read < length) {
n = input.read(bytes, start + read, length - read);
if (n == -1) {
if (read == 0) throw new java.io.EOFException();
break;
}
read += n;
}
realSize += read;
invalidate();
return this;
}
/**
* Append contents of the supplied nio ByteList up to len length onto the end of this
* ByteBuffer.
*
* @param buffer to be appended
* @param len is number of bytes you hoping to get from the ByteBuffer
*/
public void append(ByteBuffer buffer, int len) {
grow(len);
buffer.get(bytes, begin + realSize, len);
realSize += len;
invalidate();
}
/**
* Append moreBytes onto the end of the current ByteList.
*
* @param moreBytes to be added.
*/
public void append(byte[] moreBytes) {
assert moreBytes != null : "moreBytes is null";
grow(moreBytes.length);
System.arraycopy(moreBytes, 0, bytes, begin + realSize, moreBytes.length);
realSize += moreBytes.length;
invalidate();
}
/**
* Append moreBytes onto the end of the current ByteList.
*
* @param moreBytes to be added.
*/
public void append(ByteList moreBytes) {
append(moreBytes.bytes, moreBytes.begin, moreBytes.realSize);
}
/**
* Append moreBytes onto the end of the current ByteList with +index as the new begin for
* len bytes from the moreBytes ByteList.
*
* @param moreBytes to be added.
* @param index new index past current begin value
* @param len is the number of bytes to append from source ByteList
*/
public void append(ByteList moreBytes, int index, int len) {
append(moreBytes.bytes, moreBytes.begin + index, len);
}
/**
* Append moreBytes onto the end of the current ByteList with start as the new begin for
* len bytes from the moreBytes byte array.
*
* @param moreBytes to be added.
* @param start is the new begin value
* @param len is the number of bytes to append from source byte array
*/
public void append(byte[] moreBytes, int start, int len) {
assert moreBytes != null : "moreBytes is null";
// FIXME: Problems on CI box tripping on this. Re-enable later during 1.6 development.
//assert start >= 0 && (start == 0 || start < moreBytes.length) : "Invalid start";
assert len >= 0 && moreBytes.length - start >= len : "Bad length";
grow(len);
System.arraycopy(moreBytes, start, bytes, begin + realSize, len);
realSize += len;
invalidate();
}
/**
* Resize the ByteList's backing store to be length in size. Note that this forces the backing
* store to array copy regardless of ByteLists current size or contents. It essentially will
* end any COWing.
*
* @param length the new length for the backing store.
*/
public void realloc(int length) {
assert length >= 0 : "Invalid length";
assert length >= realSize : "length is too small";
byte tmp[] = new byte[length];
System.arraycopy(bytes, 0, tmp, 0, realSize);
bytes = tmp;
invalidate();
}
/**
* Return the current length of the ByteList.
*
* @return the number of bytes in this ByteList.
*/
public int length() {
return realSize;
}
// ENEBO: Wow...what happens if newLength < realSize...nasty shrinkage?
/**
* grow the bytelist to be newLength in size.
*
* @param newLength
*/
public void length(int newLength) {
// assert newLength >= realSize : "newLength is too small";
grow(newLength - realSize);
realSize = newLength;
}
/**
* Number of characters in this ByteList based on its current encoding.
*
* @return number of characters
*/
public int lengthEnc() {
return encoding.strLength(bytes, begin, begin + realSize);
}
/**
* Get the byte at index from the ByteList.
*
* @param index to retreive byte from
* @return the byte retreived
*/
public int get(int index) {
assert index >= 0 : "index must be positive";
return bytes[begin + index];
}
/**
* Get the index code point in this ByteList.
*
* @param index is the element you want
* @return the element you requested
*/
public int getEnc(int index) {
return encoding.strCodeAt(bytes, begin, begin + realSize, index);
}
/**
* Set the byte at index to be new value.
*
* @param index to set byte
* @param b is the new value.
*/
public void set(int index, int b) {
assert index >= 0 : "index must be positive";
assert begin + index < begin + realSize : "index is too large";
bytes[begin + index] = (byte)b;
invalidate();
}
/**
* Unsafe version of replace(int,int,ByteList). The contract is that these
* unsafe versions will not make sure thet beg and len indices are correct.
*/
public void unsafeReplace(int beg, int len, ByteList nbytes) {
unsafeReplace(beg, len, nbytes.bytes, nbytes.begin, nbytes.realSize);
}
/**
* Unsafe version of replace(int,int,byte[]). The contract is that these
* unsafe versions will not make sure thet beg and len indices are correct.
*/
public void unsafeReplace(int beg, int len, byte[] buf) {
unsafeReplace(beg, len, buf, 0, buf.length);
}
/**
* Unsafe version of replace(int,int,byte[],int,int). The contract is that these
* unsafe versions will not make sure thet beg and len indices are correct.
*/
public void unsafeReplace(int beg, int len, byte[] nbytes, int index, int count) {
grow(count - len);
int newSize = realSize + count - len;
System.arraycopy(bytes,beg+len,bytes,beg+count,realSize - (len+beg));
System.arraycopy(nbytes,index,bytes,beg,count);
realSize = newSize;
invalidate();
}
/**
* Replace all bytes in this ByteList with bytes from the given array.
*
* @param source bytes to use for replacement
*/
public void replace(byte[] source) {
replace(0, realSize, source, 0, source.length);
}
/**
* Replace a region of bytes in this ByteList with bytes from the given ByteList.
*
* @param targetOff offset of target region in this ByteList
* @param targetLen length of target region in this ByteList
* @param source ByteList to use for replacement
*/
public void replace(int targetOff, int targetLen, ByteList source) {
replace(targetOff, targetLen, source.bytes, source.begin, source.realSize);
}
/**
* Replace a region of bytes in this ByteList with bytes from the given array.
*
* @param targetOff offset of target region in this ByteList
* @param targetLen length of target region in this ByteList
* @param source bytes to use for replacement
*/
public void replace(int targetOff, int targetLen, byte[] source) {
replace(targetOff, targetLen, source, 0, source.length);
}
/**
* Replace a region of bytes in this ByteList with a region of bytes from the given array.
*
* @param targetOff offset of target region in this ByteList
* @param targetLen length of target region in this ByteList
* @param source bytes to use for replacement
* @param sourceOff offset of source region in the replacement bytes
* @param sourceLen length of source region in the replacement bytes
*/
public void replace(int targetOff, int targetLen, byte[] source, int sourceOff, int sourceLen) {
int newSize = realSize - targetLen + sourceLen;
ensure(newSize);
int tailSize = realSize - (targetLen + targetOff);
if (tailSize != 0) {
System.arraycopy(bytes,begin+targetOff+targetLen,bytes,begin+targetOff+sourceLen,tailSize);
}
if (sourceLen != 0) {
System.arraycopy(source,sourceOff,bytes,begin+targetOff,sourceLen);
}
realSize = newSize;
invalidate();
}
public void insert(int index, int b) {
grow(1);
System.arraycopy(bytes, index, bytes, index + 1, realSize - index);
bytes[index] = (byte)b;
realSize++;
invalidate();
}
/**
* Get the index of first occurrence of c in ByteList from the beginning of the ByteList.
*
* @param c byte to be looking for
* @return the index of the byte or -1 if not found
*/
public int indexOf(int c) {
return indexOf(c, 0);
}
/**
* Get the index of first occurrence of c in ByteList from the pos offset of the ByteList.
*
* @param c byte to be looking for
* @param pos off set from beginning of ByteList to look for byte
* @return the index of the byte or -1 if not found
*/
public int indexOf(final int c, int pos) {
// not sure if this is checked elsewhere,
// didn't see it in RubyString. RubyString does
// cast to char, so c will be >= 0.
if (c > 255)
return -1;
final byte b = (byte)(c&0xFF);
final int size = begin + realSize;
final byte[] buf = bytes;
pos += begin;
for ( ; pos < size && buf[pos] != b ; pos++ ) ;
return pos < size ? pos - begin : -1;
}
/**
* Get the index of first occurrence of Bytelist find in this ByteList.
*
* @param find the ByteList to find
* @return the index of the byte or -1 if not found
*/
public int indexOf(ByteList find) {
return indexOf(find, 0);
}
/**
* Get the index of first occurrence of Bytelist find in this ByteList starting at index i.
*
* @param find the ByteList to find
* @param i the index to start from
* @return the index of the byte or -1 if not found
*/
public int indexOf(ByteList find, int i) {
return indexOf(bytes, begin, realSize, find.bytes, find.begin, find.realSize, i);
}
/**
* Get the index of first occurrence of target in source using the offset and count parameters.
* fromIndex can be used to start beyond zero on source.
*
* @return the index of the byte or -1 if not found
*/
static int indexOf(byte[] source, int sourceOffset, int sourceCount, byte[] target, int targetOffset, int targetCount, int fromIndex) {
if (fromIndex >= sourceCount) return (targetCount == 0 ? sourceCount : -1);
if (fromIndex < 0) fromIndex = 0;
if (targetCount == 0) return fromIndex;
byte first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
if (source[i] != first) while (++i <= max && source[i] != first);
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++);
if (j == end) return i - sourceOffset;
}
}
return -1;
}
/**
* Get the index of last occurrence of c in ByteList from the end of the ByteList.
*
* @param c byte to be looking for
* @return the index of the byte or -1 if not found
*/
public int lastIndexOf(int c) {
return lastIndexOf(c, realSize - 1);
}
/**
* Get the index of last occurrence of c in ByteList from the pos offset of the ByteList.
*
* @param c byte to be looking for
* @param pos off set from end of ByteList to look for byte
* @return the index of the byte or -1 if not found
*/
public int lastIndexOf(final int c, int pos) {
// not sure if this is checked elsewhere,
// didn't see it in RubyString. RubyString does
// cast to char, so c will be >= 0.
if (c > 255) return -1;
final byte b = (byte)(c&0xFF);
final int size = begin + realSize;
pos += begin;
final byte[] buf = bytes;
if (pos >= size) {
pos = size;
} else {
pos++;
}
for ( ; --pos >= begin && buf[pos] != b ; ) ;
return pos - begin;
}
/**
* Get the index of last occurrence of find in ByteList from the end of the ByteList.
*
* @param find ByteList to be looking for
* @return the index of the byte or -1 if not found
*/
public int lastIndexOf(ByteList find) {
return lastIndexOf(find, realSize);
}
/**
* Get the index of last occurrence of find in ByteList from the end of the ByteList.
*
* @param find ByteList to be looking for
* @param pos index from end of list to search from
* @return the index of the byte or -1 if not found
*/
public int lastIndexOf(ByteList find, int pos) {
return lastIndexOf(bytes, begin, realSize, find.bytes, find.begin, find.realSize, pos);
}
/**
* Get the index of last occurrence of target in source using the offset and count parameters.
* fromIndex can be used to start beyond zero on source.
*
* @return the index of the byte or -1 if not found
*/
static int lastIndexOf(byte[] source, int sourceOffset, int sourceCount, byte[] target, int targetOffset, int targetCount, int fromIndex) {
int rightIndex = sourceCount - targetCount;
if (fromIndex < 0) return -1;
if (fromIndex > rightIndex) fromIndex = rightIndex;
if (targetCount == 0) return fromIndex;
int strLastIndex = targetOffset + targetCount - 1;
byte strLastChar = target[strLastIndex];
int min = sourceOffset + targetCount - 1;
int i = min + fromIndex;
startSearchForLastChar:
while (true) {
while (i >= min && source[i] != strLastChar) i--;
if (i < min) return -1;
int j = i - 1;
int start = j - (targetCount - 1);
int k = strLastIndex - 1;
while (j > start) {
if (source[j--] != target[k--]) {
i--;
continue startSearchForLastChar;
}
}
return start - sourceOffset + 1;
}
}
public boolean startsWith(ByteList other, int toffset) {
if (realSize == 0 || this.realSize < other.realSize + toffset) return false;
byte[]ta = bytes;
int to = begin + toffset;
byte[]pa = other.bytes;
int po = other.begin;
int pc = other.realSize;
while (--pc >= 0) if (ta[to++] != pa[po++]) return false;
return true;
}
/**
* Does this ByteList start with the supplied ByteList?
*
* @param other is the bytelist to compare with
* @return true is this ByteList starts with other
*/
public boolean startsWith(ByteList other) {
return startsWith(other, 0);
}
/**
* Does this ByteList end with the supplied ByteList?
*
* @param other is the bytelist to compare with
* @return true is this ByteList starts with other
*/
public boolean endsWith(ByteList other) {
return startsWith(other, realSize - other.realSize);
}
/**
* Does this ByteList equal the other ByteList?
*
* @param other is the bytelist to compare with
* @return true is this ByteList is the same
*/
@Override
public boolean equals(Object other) {
if (other instanceof ByteList) return equal((ByteList)other);
return false;
}
/**
* Does this ByteList equal the other ByteList?
*
* @param other is the bytelist to compare with
* @return true is this ByteList is the same
*/
public boolean equal(ByteList other) {
if (other == this) return true;
if (hash != 0 && other.hash != 0 && hash != other.hash) return false;
int first, last;
if ((last = realSize) == other.realSize) {
byte buf[] = bytes;
byte otherBuf[] = other.bytes;
// scanning from front and back simultaneously, meeting in
// the middle. the object is to get a mismatch as quickly as
// possible. alternatives might be: scan from the middle outward
// (not great because it won't pick up common variations at the
// ends until late) or sample odd bytes forward and even bytes
// backward (I like this one, but it's more expensive for
// strings that are equal; see sample_equals below).
for (first = -1;
--last > first && buf[begin + last] == otherBuf[other.begin + last] &&
++first < last && buf[begin + first] == otherBuf[other.begin + first] ; ) ;
return first >= last;
}
return false;
}
/**
* an alternative to the new version of equals, should
* detect inequality faster (in many cases), but is slow
* in the case of equal values (all bytes visited), due to
* using n+=2, n-=2 vs. ++n, --n while iterating over the array.
*/
public boolean sample_equals(Object other) {
if (other == this) return true;
if (other instanceof ByteList) {
ByteList b = (ByteList) other;
int first;
int last;
int size;
byte[] buf;
if ((size = realSize) == b.realSize) {
// scanning from front and back simultaneously, sampling odd
// bytes on the forward iteration and even bytes on the
// reverse iteration. the object is to get a mismatch as quickly
// as possible.
for (buf = bytes, first = -1, last = (size + 1) & ~1 ;
(last -= 2) >= 0 && buf[begin + last] == b.bytes[b.begin + last] &&
(first += 2) < size && buf[begin + first] == b.bytes[b.begin + first] ; ) ;
return last < 0 || first == size;
}
}
return false;
}
/**
* This comparison matches MRI comparison of Strings (rb_str_cmp).