1 // -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 // System.Text.StringBuilder
6 // Marcin Szczepanski (marcins@zipworld.com.au)
7 // Paolo Molaro (lupus@ximian.com)
10 // NOTE: In the case the buffer is only filled by 50% a new string
11 // will be returned by ToString() is cached in the '_cached_str'
12 // cache_string will also control if a string has been handed out
13 // to via ToString(). If you are chaning the code make sure that
14 // if you modify the string data set the cache_string to null.
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
19 // Copyright 2011 Xamarin Inc
21 // Permission is hereby granted, free of charge, to any person obtaining
22 // a copy of this software and associated documentation files (the
23 // "Software"), to deal in the Software without restriction, including
24 // without limitation the rights to use, copy, modify, merge, publish,
25 // distribute, sublicense, and/or sell copies of the Software, and to
26 // permit persons to whom the Software is furnished to do so, subject to
27 // the following conditions:
29 // The above copyright notice and this permission notice shall be
30 // included in all copies or substantial portions of the Software.
32 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
33 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
35 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
36 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
37 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
38 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40 using System.Runtime.Serialization;
41 using System.Runtime.CompilerServices;
42 using System.Runtime.InteropServices;
44 namespace System.Text {
48 [MonoLimitation ("Serialization format not compatible with .NET")]
49 [StructLayout (LayoutKind.Sequential)]
50 public sealed class StringBuilder : ISerializable
54 private string _cached_str;
56 private int _maxCapacity;
57 internal const int DefaultCapacity = 16;
59 public StringBuilder(string value, int startIndex, int length, int capacity)
60 : this (value, startIndex, length, capacity, Int32.MaxValue)
64 private StringBuilder(string value, int startIndex, int length, int capacity, int maxCapacity)
66 // first, check the parameters and throw appropriate exceptions if needed
70 // make sure startIndex is zero or positive
72 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex cannot be less than zero.");
74 // make sure length is zero or positive
76 throw new System.ArgumentOutOfRangeException ("length", length, "Length cannot be less than zero.");
79 throw new System.ArgumentOutOfRangeException ("capacity", capacity, "capacity must be greater than zero.");
82 throw new System.ArgumentOutOfRangeException ("maxCapacity", "maxCapacity is less than one.");
83 if (capacity > maxCapacity)
84 throw new System.ArgumentOutOfRangeException ("capacity", "Capacity exceeds maximum capacity.");
86 // make sure startIndex and length give a valid substring of value
87 // re-ordered to avoid possible integer overflow
88 if (startIndex > value.Length - length)
89 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");
92 if (maxCapacity > DefaultCapacity)
93 capacity = DefaultCapacity;
95 _str = _cached_str = String.Empty;
97 _maxCapacity = maxCapacity;
100 _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
102 String.CharCopy (_str, 0, value, startIndex, length);
107 public StringBuilder () : this (null) {}
109 public StringBuilder(int capacity) : this (String.Empty, 0, 0, capacity) {}
111 public StringBuilder(int capacity, int maxCapacity) : this (String.Empty, 0, 0, capacity, maxCapacity) { }
113 public StringBuilder (string value)
116 * This is an optimization to avoid allocating the internal string
117 * until the first Append () call.
118 * The runtime pinvoke marshalling code needs to be aware of this.
123 _length = value.Length;
124 _str = _cached_str = value;
125 _maxCapacity = Int32.MaxValue;
128 public StringBuilder( string value, int capacity) : this(value == null ? "" : value, 0, value == null ? 0 : value.Length, capacity) {}
130 public int MaxCapacity {
136 public int Capacity {
138 if (_str.Length == 0)
139 return Math.Min (_maxCapacity, DefaultCapacity);
146 throw new ArgumentException( "Capacity must be larger than length" );
148 if (value > _maxCapacity)
149 throw new ArgumentOutOfRangeException ("value", "Should be less than or equal to MaxCapacity");
151 InternalEnsureCapacity(value);
161 if( value < 0 || value > _maxCapacity)
162 throw new ArgumentOutOfRangeException();
164 if (value == _length)
167 if (value < _length) {
168 // LAMESPEC: The spec is unclear as to what to do
169 // with the capacity when truncating the string.
171 // Do as MS, keep the capacity
173 // Make sure that we invalidate any cached string.
174 InternalEnsureCapacity (value);
177 // Expand the capacity to the new length and
178 // pad the string with NULL characters.
179 Append('\0', value - _length);
184 [IndexerName("Chars")]
185 public char this [int index] {
187 if (index >= _length || index < 0)
188 throw new IndexOutOfRangeException();
194 if (index >= _length || index < 0)
195 throw new IndexOutOfRangeException();
197 if (null != _cached_str)
198 InternalEnsureCapacity (_length);
200 _str.InternalSetChar (index, value);
204 public override string ToString ()
209 if (null != _cached_str)
212 // If we only have a half-full buffer we return a new string.
213 if (_length < (_str.Length >> 1) || (_str.Length > string.LOS_limit && _length <= string.LOS_limit))
215 // use String.SubstringUnchecked instead of String.Substring
216 // as the former is guaranteed to create a new string object
217 _cached_str = _str.SubstringUnchecked (0, _length);
222 _str.InternalSetLength(_length);
227 public string ToString (int startIndex, int length)
229 // re-ordered to avoid possible integer overflow
230 if (startIndex < 0 || length < 0 || startIndex > _length - length)
231 throw new ArgumentOutOfRangeException();
233 // use String.SubstringUnchecked instead of String.Substring
234 // as the former is guaranteed to create a new string object
235 if (startIndex == 0 && length == _length)
238 return _str.SubstringUnchecked (startIndex, length);
241 public int EnsureCapacity (int capacity)
244 throw new ArgumentOutOfRangeException ("Capacity must be greater than 0." );
246 if( capacity <= _str.Length )
249 InternalEnsureCapacity (capacity);
254 public bool Equals (StringBuilder sb)
256 if (((object)sb) == null)
259 if (_length == sb.Length && _str == sb._str )
265 public StringBuilder Remove (int startIndex, int length)
267 // re-ordered to avoid possible integer overflow
268 if (startIndex < 0 || length < 0 || startIndex > _length - length)
269 throw new ArgumentOutOfRangeException();
271 if (null != _cached_str)
272 InternalEnsureCapacity (_length);
274 // Copy everything after the 'removed' part to the start
275 // of the removed part and truncate the sLength
276 if (_length - (startIndex + length) > 0)
277 String.CharCopy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
284 public StringBuilder Replace (char oldChar, char newChar)
286 return Replace( oldChar, newChar, 0, _length);
289 public StringBuilder Replace (char oldChar, char newChar, int startIndex, int count)
291 // re-ordered to avoid possible integer overflow
292 if (startIndex > _length - count || startIndex < 0 || count < 0)
293 throw new ArgumentOutOfRangeException();
295 if (null != _cached_str)
296 InternalEnsureCapacity (_str.Length);
298 for (int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
299 if( _str [replaceIterate] == oldChar )
300 _str.InternalSetChar (replaceIterate, newChar);
306 public StringBuilder Replace( string oldValue, string newValue ) {
307 return Replace (oldValue, newValue, 0, _length);
310 public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count )
312 if (oldValue == null)
313 throw new ArgumentNullException ("The old value cannot be null.");
315 if (startIndex < 0 || count < 0 || startIndex > _length - count)
316 throw new ArgumentOutOfRangeException ();
318 if (oldValue.Length == 0)
319 throw new ArgumentException ("The old value cannot be zero length.");
321 string substr = _str.Substring(startIndex, count);
322 string replace = substr.Replace(oldValue, newValue);
323 // return early if no oldValue was found
324 if ((object) replace == (object) substr)
327 InternalEnsureCapacity (replace.Length + (_length - count));
330 if (replace.Length < count)
331 String.CharCopy (_str, startIndex + replace.Length, _str, startIndex + count, _length - startIndex - count);
332 else if (replace.Length > count)
333 String.CharCopyReverse (_str, startIndex + replace.Length, _str, startIndex + count, _length - startIndex - count);
335 // copy middle part back into _str
336 String.CharCopy (_str, startIndex, replace, 0, replace.Length);
338 _length = replace.Length + (_length - count);
344 /* The Append Methods */
345 public StringBuilder Append (char[] value)
350 int needed_cap = _length + value.Length;
351 if (null != _cached_str || _str.Length < needed_cap)
352 InternalEnsureCapacity (needed_cap);
354 String.CharCopy (_str, _length, value, 0, value.Length);
355 _length = needed_cap;
360 public StringBuilder Append (string value)
365 if (_length == 0 && value.Length < _maxCapacity && value.Length > _str.Length) {
366 _length = value.Length;
367 _str = _cached_str = value;
371 int needed_cap = _length + value.Length;
372 if (null != _cached_str || _str.Length < needed_cap)
373 InternalEnsureCapacity (needed_cap);
375 String.CharCopy (_str, _length, value, 0, value.Length);
376 _length = needed_cap;
380 public StringBuilder Append (bool value) {
381 return Append (value.ToString());
384 public StringBuilder Append (byte value) {
385 return Append (value.ToString());
388 public StringBuilder Append (decimal value) {
389 return Append (value.ToString());
392 public StringBuilder Append (double value) {
393 return Append (value.ToString());
396 public StringBuilder Append (short value) {
397 return Append (value.ToString());
400 public StringBuilder Append (int value) {
401 return Append (value.ToString());
404 public StringBuilder Append (long value) {
405 return Append (value.ToString());
408 public StringBuilder Append (object value) {
412 return Append (value.ToString());
415 [CLSCompliant(false)]
416 public StringBuilder Append (sbyte value) {
417 return Append (value.ToString());
420 public StringBuilder Append (float value) {
421 return Append (value.ToString());
424 [CLSCompliant(false)]
425 public StringBuilder Append (ushort value) {
426 return Append (value.ToString());
429 [CLSCompliant(false)]
430 public StringBuilder Append (uint value) {
431 return Append (value.ToString());
434 [CLSCompliant(false)]
435 public StringBuilder Append (ulong value) {
436 return Append (value.ToString());
439 public StringBuilder Append (char value)
441 int needed_cap = _length + 1;
442 if (null != _cached_str || _str.Length < needed_cap)
443 InternalEnsureCapacity (needed_cap);
445 _str.InternalSetChar(_length, value);
446 _length = needed_cap;
451 public StringBuilder Append (char value, int repeatCount)
453 if( repeatCount < 0 )
454 throw new ArgumentOutOfRangeException();
456 InternalEnsureCapacity (_length + repeatCount);
458 for (int i = 0; i < repeatCount; i++)
459 _str.InternalSetChar (_length++, value);
464 public StringBuilder Append( char[] value, int startIndex, int charCount )
467 if (!(startIndex == 0 && charCount == 0))
468 throw new ArgumentNullException ("value");
473 if ((charCount < 0 || startIndex < 0) || (startIndex > value.Length - charCount))
474 throw new ArgumentOutOfRangeException();
476 int needed_cap = _length + charCount;
477 InternalEnsureCapacity (needed_cap);
479 String.CharCopy (_str, _length, value, startIndex, charCount);
480 _length = needed_cap;
485 public StringBuilder Append (string value, int startIndex, int count)
488 if (startIndex != 0 && count != 0)
489 throw new ArgumentNullException ("value");
494 if ((count < 0 || startIndex < 0) || (startIndex > value.Length - count))
495 throw new ArgumentOutOfRangeException();
497 int needed_cap = _length + count;
498 if (null != _cached_str || _str.Length < needed_cap)
499 InternalEnsureCapacity (needed_cap);
501 String.CharCopy (_str, _length, value, startIndex, count);
503 _length = needed_cap;
508 internal unsafe StringBuilder Append (char* value, int valueCount)
510 int needed_cap = _length + valueCount;
511 InternalEnsureCapacity (needed_cap);
513 fixed (char* src = _str) {
514 String.CharCopy (src + _length, value, valueCount);
516 _length = needed_cap;
521 public StringBuilder Clear ()
528 public StringBuilder AppendLine ()
530 return Append (System.Environment.NewLine);
534 public StringBuilder AppendLine (string value)
536 return Append (value).Append (System.Environment.NewLine);
539 public StringBuilder AppendFormat (string format, params object[] args)
541 return AppendFormat (null, format, args);
544 public StringBuilder AppendFormat (IFormatProvider provider,
546 params object[] args)
548 String.FormatHelper (this, provider, format, args);
553 StringBuilder AppendFormat (string format, object arg0)
555 return AppendFormat (null, format, new object [] { arg0 });
559 StringBuilder AppendFormat (string format, object arg0, object arg1)
561 return AppendFormat (null, format, new object [] { arg0, arg1 });
565 StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
567 return AppendFormat (null, format, new object [] { arg0, arg1, arg2 });
570 /* The Insert Functions */
572 public StringBuilder Insert (int index, char[] value)
574 return Insert (index, new string (value));
577 public StringBuilder Insert (int index, string value)
579 if( index > _length || index < 0)
580 throw new ArgumentOutOfRangeException();
582 if (value == null || value.Length == 0)
585 InternalEnsureCapacity (_length + value.Length);
587 // Move everything to the right of the insert point across
588 String.CharCopyReverse (_str, index + value.Length, _str, index, _length - index);
590 // Copy in stuff from the insert buffer
591 String.CharCopy (_str, index, value, 0, value.Length);
593 _length += value.Length;
598 public StringBuilder Insert( int index, bool value ) {
599 return Insert (index, value.ToString());
602 public StringBuilder Insert( int index, byte value ) {
603 return Insert (index, value.ToString());
606 public StringBuilder Insert( int index, char value)
608 if (index > _length || index < 0)
609 throw new ArgumentOutOfRangeException ("index");
611 InternalEnsureCapacity (_length + 1);
613 // Move everything to the right of the insert point across
614 String.CharCopyReverse (_str, index + 1, _str, index, _length - index);
616 _str.InternalSetChar (index, value);
622 public StringBuilder Insert( int index, decimal value ) {
623 return Insert (index, value.ToString());
626 public StringBuilder Insert( int index, double value ) {
627 return Insert (index, value.ToString());
630 public StringBuilder Insert( int index, short value ) {
631 return Insert (index, value.ToString());
634 public StringBuilder Insert( int index, int value ) {
635 return Insert (index, value.ToString());
638 public StringBuilder Insert( int index, long value ) {
639 return Insert (index, value.ToString());
642 public StringBuilder Insert( int index, object value ) {
643 return Insert (index, value.ToString());
646 [CLSCompliant(false)]
647 public StringBuilder Insert( int index, sbyte value ) {
648 return Insert (index, value.ToString() );
651 public StringBuilder Insert (int index, float value) {
652 return Insert (index, value.ToString() );
655 [CLSCompliant(false)]
656 public StringBuilder Insert (int index, ushort value) {
657 return Insert (index, value.ToString() );
660 [CLSCompliant(false)]
661 public StringBuilder Insert (int index, uint value) {
662 return Insert ( index, value.ToString() );
665 [CLSCompliant(false)]
666 public StringBuilder Insert (int index, ulong value) {
667 return Insert ( index, value.ToString() );
670 public StringBuilder Insert (int index, string value, int count)
672 // LAMESPEC: The spec says to throw an exception if
673 // count < 0, while MS throws even for count < 1!
675 throw new ArgumentOutOfRangeException();
677 if (value != null && value != String.Empty)
678 for (int insertCount = 0; insertCount < count; insertCount++)
679 Insert( index, value );
684 public StringBuilder Insert (int index, char [] value, int startIndex, int charCount)
687 if (startIndex == 0 && charCount == 0)
690 throw new ArgumentNullException ("value");
693 if (charCount < 0 || startIndex < 0 || startIndex > value.Length - charCount)
694 throw new ArgumentOutOfRangeException ();
696 return Insert (index, new String (value, startIndex, charCount));
699 private void InternalEnsureCapacity (int size)
701 if (size > _str.Length || (object) _cached_str == (object) _str) {
702 int capacity = _str.Length;
704 // Try double buffer, if that doesn't work, set the length as capacity
705 if (size > capacity) {
707 // The first time a string is appended, we just set _cached_str
708 // and _str to it. This allows us to do some optimizations.
709 // Below, we take this into account.
710 if ((object) _cached_str == (object) _str && capacity < DefaultCapacity)
711 capacity = DefaultCapacity;
713 capacity = capacity << 1;
717 if (capacity >= Int32.MaxValue || capacity < 0)
718 capacity = Int32.MaxValue;
720 if (capacity > _maxCapacity && size <= _maxCapacity)
721 capacity = _maxCapacity;
723 if (capacity > _maxCapacity)
724 throw new ArgumentOutOfRangeException ("size", "capacity was less than the current size.");
727 string tmp = String.InternalAllocateStr (capacity);
729 String.CharCopy (tmp, 0, _str, 0, _length);
738 public void CopyTo (int sourceIndex, char [] destination, int destinationIndex, int count)
740 if (destination == null)
741 throw new ArgumentNullException ("destination");
742 if ((Length - count < sourceIndex) ||
743 (destination.Length -count < destinationIndex) ||
744 (sourceIndex < 0 || destinationIndex < 0 || count < 0))
745 throw new ArgumentOutOfRangeException ();
747 for (int i = 0; i < count; i++)
748 destination [destinationIndex+i] = _str [sourceIndex+i];
751 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
753 info.AddValue ("m_MaxCapacity", _maxCapacity);
754 info.AddValue ("Capacity", Capacity);
755 info.AddValue ("m_StringValue", ToString ());
756 info.AddValue ("m_currentThread", 0);
759 StringBuilder (SerializationInfo info, StreamingContext context)
761 string s = info.GetString ("m_StringValue");
765 _str = _cached_str = s;
767 _maxCapacity = info.GetInt32 ("m_MaxCapacity");
768 if (_maxCapacity < 0)
769 _maxCapacity = Int32.MaxValue;
770 Capacity = info.GetInt32 ("Capacity");