• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
TextBuffer(BufferRecycler allocator)123     public TextBuffer(BufferRecycler allocator) {
124         _allocator = allocator;
125     }
126 
127     /**
128      * @since 2.10
129      */
TextBuffer(BufferRecycler allocator, char[] initialSegment)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      */
fromInitial(char[] initialSegment)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      */
releaseBuffers()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      */
resetWithEmpty()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      */
resetWith(char ch)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      */
resetWithShared(char[] buf, int start, int len)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 
resetWithCopy(char[] buf, int start, int len)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      */
resetWithCopy(String text, int start, int len)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 
resetWithString(String value)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      */
getBufferWithoutReset()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      */
buf(int needed)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 
clearSegments()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      */
size()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 
getTextOffset()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      */
hasTextAsCharacters()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      */
getTextBuffer()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 
contentsAsString()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 
contentsAsArray()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      */
contentsAsDecimal()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      */
contentsAsDouble()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      */
contentsAsInt(boolean neg)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      */
contentsAsLong(boolean neg)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      */
contentsToWriter(Writer w)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      */
ensureNotShared()578     public void ensureNotShared() {
579         if (_inputStart >= 0) {
580             unshare(16);
581         }
582     }
583 
append(char c)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 
append(char[] c, int start, int len)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 
append(String str, int offset, int len)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 
getCurrentSegment()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 
emptyAndGetCurrentSegment()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 
getCurrentSegmentSize()719     public int getCurrentSegmentSize() { return _currentSize; }
setCurrentLength(int len)720     public void setCurrentLength(int len) { _currentSize = len; }
721 
722     /**
723      * @since 2.6
724      */
setCurrentAndReturn(int len)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 
finishCurrentSegment()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      */
expandCurrentSegment()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      */
expandCurrentSegment(int minSize)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      */
toString()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      */
unshare(int needExtra)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      */
expand(int minNewSegmentSize)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 
resultArray()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 
carr(int len)901     private char[] carr(int len) { return new char[len]; }
902 }
903