1 package com.fasterxml.jackson.core.util;
2
3 import java.io.*;
4 import java.math.BigDecimal;
5 import java.util.*;
6
7 import com.fasterxml.jackson.core.io.NumberInput;
8
9 /**
10 * TextBuffer is a class similar to {@link StringBuffer}, with
11 * following differences:
12 *<ul>
13 * <li>TextBuffer uses segments character arrays, to avoid having
14 * to do additional array copies when array is not big enough.
15 * This means that only reallocating that is necessary is done only once:
16 * if and when caller
17 * wants to access contents in a linear array (char[], String).
18 * </li>
19 * <li>TextBuffer can also be initialized in "shared mode", in which
20 * it will just act as a wrapper to a single char array managed
21 * by another object (like parser that owns it)
22 * </li>
23 * <li>TextBuffer is not synchronized.
24 * </li>
25 * </ul>
26 */
27 public final class TextBuffer
28 {
29 final static char[] NO_CHARS = new char[0];
30
31 /**
32 * Let's start with sizable but not huge buffer, will grow as necessary
33 *<p>
34 * Reduced from 1000 down to 500 in 2.10.
35 */
36 final static int MIN_SEGMENT_LEN = 500;
37
38 /**
39 * Let's limit maximum segment length to something sensible.
40 * For 2.10, let's limit to using 64kc chunks (128 kB) -- was 256kC/512kB up to 2.9
41 */
42 final static int MAX_SEGMENT_LEN = 0x10000;
43
44 /*
45 /**********************************************************
46 /* Configuration:
47 /**********************************************************
48 */
49
50 private final BufferRecycler _allocator;
51
52 /*
53 /**********************************************************
54 /* Shared input buffers
55 /**********************************************************
56 */
57
58 /**
59 * Shared input buffer; stored here in case some input can be returned
60 * as is, without being copied to collector's own buffers. Note that
61 * this is read-only for this Object.
62 */
63 private char[] _inputBuffer;
64
65 /**
66 * Character offset of first char in input buffer; -1 to indicate
67 * that input buffer currently does not contain any useful char data
68 */
69 private int _inputStart;
70
71 private int _inputLen;
72
73 /*
74 /**********************************************************
75 /* Aggregation segments (when not using input buf)
76 /**********************************************************
77 */
78
79 /**
80 * List of segments prior to currently active segment.
81 */
82 private ArrayList<char[]> _segments;
83
84 /**
85 * Flag that indicates whether _seqments is non-empty
86 */
87 private boolean _hasSegments;
88
89 // // // Currently used segment; not (yet) contained in _seqments
90
91 /**
92 * Amount of characters in segments in {@link _segments}
93 */
94 private int _segmentSize;
95
96 private char[] _currentSegment;
97
98 /**
99 * Number of characters in currently active (last) segment
100 */
101 private int _currentSize;
102
103 /*
104 /**********************************************************
105 /* Caching of results
106 /**********************************************************
107 */
108
109 /**
110 * String that will be constructed when the whole contents are
111 * needed; will be temporarily stored in case asked for again.
112 */
113 private String _resultString;
114
115 private char[] _resultArray;
116
117 /*
118 /**********************************************************
119 /* Life-cycle
120 /**********************************************************
121 */
122
123 public TextBuffer(BufferRecycler allocator) {
124 _allocator = allocator;
125 }
126
127 /**
128 * @since 2.10
129 */
130 protected TextBuffer(BufferRecycler allocator, char[] initialSegment) {
131 _allocator = allocator;
132 _currentSegment = initialSegment;
133 _currentSize = initialSegment.length;
134 _inputStart = -1;
135 }
136
137 /**
138 * Factory method for constructing an instance with no allocator, and
139 * with initial full segment.
140 *
141 * @since 2.10
142 */
143 public static TextBuffer fromInitial(char[] initialSegment) {
144 return new TextBuffer(null, initialSegment);
145 }
146
147 /**
148 * Method called to indicate that the underlying buffers should now
149 * be recycled if they haven't yet been recycled. Although caller
150 * can still use this text buffer, it is not advisable to call this
151 * method if that is likely, since next time a buffer is needed,
152 * buffers need to reallocated.
153 *<p>
154 * Note: since Jackson 2.11, calling this method will NOT clear already
155 * aggregated contents (that is, {@code _currentSegment}, to retain
156 * current token text if (but only if!) already aggregated.
157 */
158 public void releaseBuffers()
159 {
160 // inlined `resetWithEmpty()` (except leaving `_resultString` as-is
161 {
162 _inputStart = -1;
163 _currentSize = 0;
164 _inputLen = 0;
165
166 _inputBuffer = null;
167 // note: _resultString retained (see https://github.com/FasterXML/jackson-databind/issues/2635
168 // for reason)
169 _resultArray = null; // should this be retained too?
170
171 if (_hasSegments) {
172 clearSegments();
173 }
174 }
175
176 if (_allocator != null) {
177 if (_currentSegment != null) {
178 // And then return that array
179 char[] buf = _currentSegment;
180 _currentSegment = null;
181 _allocator.releaseCharBuffer(BufferRecycler.CHAR_TEXT_BUFFER, buf);
182 }
183 }
184 }
185
186 /**
187 * Method called to clear out any content text buffer may have, and
188 * initializes buffer to use non-shared data.
189 */
190 public void resetWithEmpty()
191 {
192 _inputStart = -1; // indicates shared buffer not used
193 _currentSize = 0;
194 _inputLen = 0;
195
196 _inputBuffer = null;
197 _resultString = null;
198 _resultArray = null;
199
200 // And then reset internal input buffers, if necessary:
201 if (_hasSegments) {
202 clearSegments();
203 }
204 }
205
206 /**
207 * @since 2.9
208 */
209 public void resetWith(char ch)
210 {
211 _inputStart = -1;
212 _inputLen = 0;
213
214 _resultString = null;
215 _resultArray = null;
216
217 if (_hasSegments) {
218 clearSegments();
219 } else if (_currentSegment == null) {
220 _currentSegment = buf(1);
221 }
222 _currentSegment[0] = ch;
223 _currentSize = _segmentSize = 1;
224 }
225
226 /**
227 * Method called to initialize the buffer with a shared copy of data;
228 * this means that buffer will just have pointers to actual data. It
229 * also means that if anything is to be appended to the buffer, it
230 * will first have to unshare it (make a local copy).
231 */
232 public void resetWithShared(char[] buf, int start, int len)
233 {
234 // First, let's clear intermediate values, if any:
235 _resultString = null;
236 _resultArray = null;
237
238 // Then let's mark things we need about input buffer
239 _inputBuffer = buf;
240 _inputStart = start;
241 _inputLen = len;
242
243 // And then reset internal input buffers, if necessary:
244 if (_hasSegments) {
245 clearSegments();
246 }
247 }
248
249 public void resetWithCopy(char[] buf, int start, int len)
250 {
251 _inputBuffer = null;
252 _inputStart = -1; // indicates shared buffer not used
253 _inputLen = 0;
254
255 _resultString = null;
256 _resultArray = null;
257
258 // And then reset internal input buffers, if necessary:
259 if (_hasSegments) {
260 clearSegments();
261 } else if (_currentSegment == null) {
262 _currentSegment = buf(len);
263 }
264 _currentSize = _segmentSize = 0;
265 append(buf, start, len);
266 }
267
268 /**
269 * @since 2.9
270 */
271 public void resetWithCopy(String text, int start, int len)
272 {
273 _inputBuffer = null;
274 _inputStart = -1;
275 _inputLen = 0;
276
277 _resultString = null;
278 _resultArray = null;
279
280 if (_hasSegments) {
281 clearSegments();
282 } else if (_currentSegment == null) {
283 _currentSegment = buf(len);
284 }
285 _currentSize = _segmentSize = 0;
286 append(text, start, len);
287 }
288
289 public void resetWithString(String value)
290 {
291 _inputBuffer = null;
292 _inputStart = -1;
293 _inputLen = 0;
294
295 _resultString = value;
296 _resultArray = null;
297
298 if (_hasSegments) {
299 clearSegments();
300 }
301 _currentSize = 0;
302
303 }
304
305 /**
306 * @since 2.9
307 */
308 public char[] getBufferWithoutReset() {
309 return _currentSegment;
310 }
311
312 /**
313 * Helper method used to find a buffer to use, ideally one
314 * recycled earlier.
315 */
316 private char[] buf(int needed)
317 {
318 if (_allocator != null) {
319 return _allocator.allocCharBuffer(BufferRecycler.CHAR_TEXT_BUFFER, needed);
320 }
321 return new char[Math.max(needed, MIN_SEGMENT_LEN)];
322 }
323
324 private void clearSegments()
325 {
326 _hasSegments = false;
327 /* Let's start using _last_ segment from list; for one, it's
328 * the biggest one, and it's also most likely to be cached
329 */
330 /* 28-Aug-2009, tatu: Actually, the current segment should
331 * be the biggest one, already
332 */
333 //_currentSegment = _segments.get(_segments.size() - 1);
334 _segments.clear();
335 _currentSize = _segmentSize = 0;
336 }
337
338 /*
339 /**********************************************************
340 /* Accessors for implementing public interface
341 /**********************************************************
342 */
343
344 /**
345 * @return Number of characters currently stored by this collector
346 */
347 public int size() {
348 if (_inputStart >= 0) { // shared copy from input buf
349 return _inputLen;
350 }
351 if (_resultArray != null) {
352 return _resultArray.length;
353 }
354 if (_resultString != null) {
355 return _resultString.length();
356 }
357 // local segmented buffers
358 return _segmentSize + _currentSize;
359 }
360
361 public int getTextOffset() {
362 /* Only shared input buffer can have non-zero offset; buffer
363 * segments start at 0, and if we have to create a combo buffer,
364 * that too will start from beginning of the buffer
365 */
366 return (_inputStart >= 0) ? _inputStart : 0;
367 }
368
369 /**
370 * Method that can be used to check whether textual contents can
371 * be efficiently accessed using {@link #getTextBuffer}.
372 */
373 public boolean hasTextAsCharacters()
374 {
375 // if we have array in some form, sure
376 if (_inputStart >= 0 || _resultArray != null) return true;
377 // not if we have String as value
378 if (_resultString != null) return false;
379 return true;
380 }
381
382 /**
383 * Accessor that may be used to get the contents of this buffer in a single
384 * <code>char</code> array regardless of whether they were collected in a segmented
385 * fashion or not.
386 */
387 public char[] getTextBuffer()
388 {
389 // Are we just using shared input buffer?
390 if (_inputStart >= 0) return _inputBuffer;
391 if (_resultArray != null) return _resultArray;
392 if (_resultString != null) {
393 return (_resultArray = _resultString.toCharArray());
394 }
395 // Nope; but does it fit in just one segment?
396 if (!_hasSegments) {
397 return (_currentSegment == null) ? NO_CHARS : _currentSegment;
398 }
399 // Nope, need to have/create a non-segmented array and return it
400 return contentsAsArray();
401 }
402
403 /*
404 /**********************************************************
405 /* Other accessors:
406 /**********************************************************
407 */
408
409 public String contentsAsString()
410 {
411 if (_resultString == null) {
412 // Has array been requested? Can make a shortcut, if so:
413 if (_resultArray != null) {
414 _resultString = new String(_resultArray);
415 } else {
416 // Do we use shared array?
417 if (_inputStart >= 0) {
418 if (_inputLen < 1) {
419 return (_resultString = "");
420 }
421 _resultString = new String(_inputBuffer, _inputStart, _inputLen);
422 } else { // nope... need to copy
423 // But first, let's see if we have just one buffer
424 int segLen = _segmentSize;
425 int currLen = _currentSize;
426
427 if (segLen == 0) { // yup
428 _resultString = (currLen == 0) ? "" : new String(_currentSegment, 0, currLen);
429 } else { // no, need to combine
430 StringBuilder sb = new StringBuilder(segLen + currLen);
431 // First stored segments
432 if (_segments != null) {
433 for (int i = 0, len = _segments.size(); i < len; ++i) {
434 char[] curr = _segments.get(i);
435 sb.append(curr, 0, curr.length);
436 }
437 }
438 // And finally, current segment:
439 sb.append(_currentSegment, 0, _currentSize);
440 _resultString = sb.toString();
441 }
442 }
443 }
444 }
445
446 return _resultString;
447 }
448
449 public char[] contentsAsArray() {
450 char[] result = _resultArray;
451 if (result == null) {
452 _resultArray = result = resultArray();
453 }
454 return result;
455 }
456
457 /**
458 * Convenience method for converting contents of the buffer
459 * into a {@link BigDecimal}.
460 */
461 public BigDecimal contentsAsDecimal() throws NumberFormatException
462 {
463 // Already got a pre-cut array?
464 if (_resultArray != null) {
465 return NumberInput.parseBigDecimal(_resultArray);
466 }
467 // Or a shared buffer?
468 if ((_inputStart >= 0) && (_inputBuffer != null)) {
469 return NumberInput.parseBigDecimal(_inputBuffer, _inputStart, _inputLen);
470 }
471 // Or if not, just a single buffer (the usual case)
472 if ((_segmentSize == 0) && (_currentSegment != null)) {
473 return NumberInput.parseBigDecimal(_currentSegment, 0, _currentSize);
474 }
475 // If not, let's just get it aggregated...
476 return NumberInput.parseBigDecimal(contentsAsArray());
477 }
478
479 /**
480 * Convenience method for converting contents of the buffer
481 * into a Double value.
482 */
483 public double contentsAsDouble() throws NumberFormatException {
484 return NumberInput.parseDouble(contentsAsString());
485 }
486
487 /**
488 * Specialized convenience method that will decode a 32-bit int,
489 * of at most 9 digits (and possible leading minus sign).
490 *
491 * @param neg Whether contents start with a minus sign
492 *
493 * @since 2.9
494 */
495 public int contentsAsInt(boolean neg) {
496 if ((_inputStart >= 0) && (_inputBuffer != null)) {
497 if (neg) {
498 return -NumberInput.parseInt(_inputBuffer, _inputStart+1, _inputLen-1);
499 }
500 return NumberInput.parseInt(_inputBuffer, _inputStart, _inputLen);
501 }
502 if (neg) {
503 return -NumberInput.parseInt(_currentSegment, 1, _currentSize-1);
504 }
505 return NumberInput.parseInt(_currentSegment, 0, _currentSize);
506 }
507
508 /**
509 * Specialized convenience method that will decode a 64-bit int,
510 * of at most 18 digits (and possible leading minus sign).
511 *
512 * @param neg Whether contents start with a minus sign
513 *
514 * @since 2.9
515 */
516 public long contentsAsLong(boolean neg) {
517 if ((_inputStart >= 0) && (_inputBuffer != null)) {
518 if (neg) {
519 return -NumberInput.parseLong(_inputBuffer, _inputStart+1, _inputLen-1);
520 }
521 return NumberInput.parseLong(_inputBuffer, _inputStart, _inputLen);
522 }
523 if (neg) {
524 return -NumberInput.parseLong(_currentSegment, 1, _currentSize-1);
525 }
526 return NumberInput.parseLong(_currentSegment, 0, _currentSize);
527 }
528
529 /**
530 * @since 2.8
531 */
532 public int contentsToWriter(Writer w) throws IOException
533 {
534 if (_resultArray != null) {
535 w.write(_resultArray);
536 return _resultArray.length;
537 }
538 if (_resultString != null) { // Can take a shortcut...
539 w.write(_resultString);
540 return _resultString.length();
541 }
542 // Do we use shared array?
543 if (_inputStart >= 0) {
544 final int len = _inputLen;
545 if (len > 0) {
546 w.write(_inputBuffer, _inputStart, len);
547 }
548 return len;
549 }
550 // nope, not shared
551 int total = 0;
552 if (_segments != null) {
553 for (int i = 0, end = _segments.size(); i < end; ++i) {
554 char[] curr = _segments.get(i);
555 int currLen = curr.length;
556 w.write(curr, 0, currLen);
557 total += currLen;
558 }
559 }
560 int len = _currentSize;
561 if (len > 0) {
562 w.write(_currentSegment, 0, len);
563 total += len;
564 }
565 return total;
566 }
567
568 /*
569 /**********************************************************
570 /* Public mutators:
571 /**********************************************************
572 */
573
574 /**
575 * Method called to make sure that buffer is not using shared input
576 * buffer; if it is, it will copy such contents to private buffer.
577 */
578 public void ensureNotShared() {
579 if (_inputStart >= 0) {
580 unshare(16);
581 }
582 }
583
584 public void append(char c) {
585 // Using shared buffer so far?
586 if (_inputStart >= 0) {
587 unshare(16);
588 }
589 _resultString = null;
590 _resultArray = null;
591 // Room in current segment?
592 char[] curr = _currentSegment;
593 if (_currentSize >= curr.length) {
594 expand(1);
595 curr = _currentSegment;
596 }
597 curr[_currentSize++] = c;
598 }
599
600 public void append(char[] c, int start, int len)
601 {
602 // Can't append to shared buf (sanity check)
603 if (_inputStart >= 0) {
604 unshare(len);
605 }
606 _resultString = null;
607 _resultArray = null;
608
609 // Room in current segment?
610 char[] curr = _currentSegment;
611 int max = curr.length - _currentSize;
612
613 if (max >= len) {
614 System.arraycopy(c, start, curr, _currentSize, len);
615 _currentSize += len;
616 return;
617 }
618 // No room for all, need to copy part(s):
619 if (max > 0) {
620 System.arraycopy(c, start, curr, _currentSize, max);
621 start += max;
622 len -= max;
623 }
624 // And then allocate new segment; we are guaranteed to now
625 // have enough room in segment.
626 do {
627 expand(len);
628 int amount = Math.min(_currentSegment.length, len);
629 System.arraycopy(c, start, _currentSegment, 0, amount);
630 _currentSize += amount;
631 start += amount;
632 len -= amount;
633 } while (len > 0);
634 }
635
636 public void append(String str, int offset, int len)
637 {
638 // Can't append to shared buf (sanity check)
639 if (_inputStart >= 0) {
640 unshare(len);
641 }
642 _resultString = null;
643 _resultArray = null;
644
645 // Room in current segment?
646 char[] curr = _currentSegment;
647 int max = curr.length - _currentSize;
648 if (max >= len) {
649 str.getChars(offset, offset+len, curr, _currentSize);
650 _currentSize += len;
651 return;
652 }
653 // No room for all, need to copy part(s):
654 if (max > 0) {
655 str.getChars(offset, offset+max, curr, _currentSize);
656 len -= max;
657 offset += max;
658 }
659 // And then allocate new segment; we are guaranteed to now
660 // have enough room in segment.
661 do {
662 expand(len);
663 int amount = Math.min(_currentSegment.length, len);
664 str.getChars(offset, offset+amount, _currentSegment, 0);
665 _currentSize += amount;
666 offset += amount;
667 len -= amount;
668 } while (len > 0);
669 }
670
671 /*
672 /**********************************************************
673 /* Raw access, for high-performance use:
674 /**********************************************************
675 */
676
677 public char[] getCurrentSegment()
678 {
679 /* Since the intention of the caller is to directly add stuff into
680 * buffers, we should NOT have anything in shared buffer... ie. may
681 * need to unshare contents.
682 */
683 if (_inputStart >= 0) {
684 unshare(1);
685 } else {
686 char[] curr = _currentSegment;
687 if (curr == null) {
688 _currentSegment = buf(0);
689 } else if (_currentSize >= curr.length) {
690 // Plus, we better have room for at least one more char
691 expand(1);
692 }
693 }
694 return _currentSegment;
695 }
696
697 public char[] emptyAndGetCurrentSegment()
698 {
699 // inlined 'resetWithEmpty()'
700 _inputStart = -1; // indicates shared buffer not used
701 _currentSize = 0;
702 _inputLen = 0;
703
704 _inputBuffer = null;
705 _resultString = null;
706 _resultArray = null;
707
708 // And then reset internal input buffers, if necessary:
709 if (_hasSegments) {
710 clearSegments();
711 }
712 char[] curr = _currentSegment;
713 if (curr == null) {
714 _currentSegment = curr = buf(0);
715 }
716 return curr;
717 }
718
719 public int getCurrentSegmentSize() { return _currentSize; }
720 public void setCurrentLength(int len) { _currentSize = len; }
721
722 /**
723 * @since 2.6
724 */
725 public String setCurrentAndReturn(int len) {
726 _currentSize = len;
727 // We can simplify handling here compared to full `contentsAsString()`:
728 if (_segmentSize > 0) { // longer text; call main method
729 return contentsAsString();
730 }
731 // more common case: single segment
732 int currLen = _currentSize;
733 String str = (currLen == 0) ? "" : new String(_currentSegment, 0, currLen);
734 _resultString = str;
735 return str;
736 }
737
738 public char[] finishCurrentSegment() {
739 if (_segments == null) {
740 _segments = new ArrayList<char[]>();
741 }
742 _hasSegments = true;
743 _segments.add(_currentSegment);
744 int oldLen = _currentSegment.length;
745 _segmentSize += oldLen;
746 _currentSize = 0;
747
748 // Let's grow segments by 50%
749 int newLen = oldLen + (oldLen >> 1);
750 if (newLen < MIN_SEGMENT_LEN) {
751 newLen = MIN_SEGMENT_LEN;
752 } else if (newLen > MAX_SEGMENT_LEN) {
753 newLen = MAX_SEGMENT_LEN;
754 }
755 char[] curr = carr(newLen);
756 _currentSegment = curr;
757 return curr;
758 }
759
760 /**
761 * Method called to expand size of the current segment, to
762 * accommodate for more contiguous content. Usually only
763 * used when parsing tokens like names if even then.
764 */
765 public char[] expandCurrentSegment()
766 {
767 final char[] curr = _currentSegment;
768 // Let's grow by 50% by default
769 final int len = curr.length;
770 int newLen = len + (len >> 1);
771 // but above intended maximum, slow to increase by 25%
772 if (newLen > MAX_SEGMENT_LEN) {
773 newLen = len + (len >> 2);
774 }
775 return (_currentSegment = Arrays.copyOf(curr, newLen));
776 }
777
778 /**
779 * Method called to expand size of the current segment, to
780 * accommodate for more contiguous content. Usually only
781 * used when parsing tokens like names if even then.
782 *
783 * @param minSize Required minimum strength of the current segment
784 *
785 * @since 2.4.0
786 */
787 public char[] expandCurrentSegment(int minSize) {
788 char[] curr = _currentSegment;
789 if (curr.length >= minSize) return curr;
790 _currentSegment = curr = Arrays.copyOf(curr, minSize);
791 return curr;
792 }
793
794 /*
795 /**********************************************************
796 /* Standard methods:
797 /**********************************************************
798 */
799
800 /**
801 * Note: calling this method may not be as efficient as calling
802 * {@link #contentsAsString}, since it's not guaranteed that resulting
803 * String is cached.
804 */
805 @Override public String toString() { return contentsAsString(); }
806
807 /*
808 /**********************************************************
809 /* Internal methods:
810 /**********************************************************
811 */
812
813 /**
814 * Method called if/when we need to append content when we have been
815 * initialized to use shared buffer.
816 */
817 private void unshare(int needExtra)
818 {
819 int sharedLen = _inputLen;
820 _inputLen = 0;
821 char[] inputBuf = _inputBuffer;
822 _inputBuffer = null;
823 int start = _inputStart;
824 _inputStart = -1;
825
826 // Is buffer big enough, or do we need to reallocate?
827 int needed = sharedLen+needExtra;
828 if (_currentSegment == null || needed > _currentSegment.length) {
829 _currentSegment = buf(needed);
830 }
831 if (sharedLen > 0) {
832 System.arraycopy(inputBuf, start, _currentSegment, 0, sharedLen);
833 }
834 _segmentSize = 0;
835 _currentSize = sharedLen;
836 }
837
838 /**
839 * Method called when current segment is full, to allocate new
840 * segment.
841 */
842 private void expand(int minNewSegmentSize)
843 {
844 // First, let's move current segment to segment list:
845 if (_segments == null) {
846 _segments = new ArrayList<char[]>();
847 }
848 char[] curr = _currentSegment;
849 _hasSegments = true;
850 _segments.add(curr);
851 _segmentSize += curr.length;
852 _currentSize = 0;
853 int oldLen = curr.length;
854
855 // Let's grow segments by 50% minimum
856 int newLen = oldLen + (oldLen >> 1);
857 if (newLen < MIN_SEGMENT_LEN) {
858 newLen = MIN_SEGMENT_LEN;
859 } else if (newLen > MAX_SEGMENT_LEN) {
860 newLen = MAX_SEGMENT_LEN;
861 }
862 _currentSegment = carr(newLen);
863 }
864
865 private char[] resultArray()
866 {
867 if (_resultString != null) { // Can take a shortcut...
868 return _resultString.toCharArray();
869 }
870 // Do we use shared array?
871 if (_inputStart >= 0) {
872 final int len = _inputLen;
873 if (len < 1) {
874 return NO_CHARS;
875 }
876 final int start = _inputStart;
877 if (start == 0) {
878 return Arrays.copyOf(_inputBuffer, len);
879 }
880 return Arrays.copyOfRange(_inputBuffer, start, start+len);
881 }
882 // nope, not shared
883 int size = size();
884 if (size < 1) {
885 return NO_CHARS;
886 }
887 int offset = 0;
888 final char[] result = carr(size);
889 if (_segments != null) {
890 for (int i = 0, len = _segments.size(); i < len; ++i) {
891 char[] curr = _segments.get(i);
892 int currLen = curr.length;
893 System.arraycopy(curr, 0, result, offset, currLen);
894 offset += currLen;
895 }
896 }
897 System.arraycopy(_currentSegment, 0, result, offset, _currentSize);
898 return result;
899 }
900
901 private char[] carr(int len) { return new char[len]; }
902 }
903