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