StringBuilder.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. // -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2. //
  3. // System.Text.StringBuilder
  4. //
  5. // Authors:
  6. // Marcin Szczepanski ([email protected])
  7. // Paolo Molaro ([email protected])
  8. // Patrik Torstensson
  9. //
  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.
  15. //
  16. //
  17. // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  18. //
  19. // Permission is hereby granted, free of charge, to any person obtaining
  20. // a copy of this software and associated documentation files (the
  21. // "Software"), to deal in the Software without restriction, including
  22. // without limitation the rights to use, copy, modify, merge, publish,
  23. // distribute, sublicense, and/or sell copies of the Software, and to
  24. // permit persons to whom the Software is furnished to do so, subject to
  25. // the following conditions:
  26. //
  27. // The above copyright notice and this permission notice shall be
  28. // included in all copies or substantial portions of the Software.
  29. //
  30. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  31. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  32. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  33. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  34. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  35. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  36. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  37. //
  38. using System.Runtime.CompilerServices;
  39. namespace System.Text {
  40. [Serializable]
  41. [MonoTODO ("Fix serialization compatibility with MS.NET")]
  42. public sealed class StringBuilder
  43. {
  44. private int _length;
  45. private string _str = null;
  46. private string _cached_str = null;
  47. private int _maxCapacity = Int32.MaxValue;
  48. private const int constDefaultCapacity = 16;
  49. public StringBuilder(string value, int startIndex, int length, int capacity)
  50. {
  51. // first, check the parameters and throw appropriate exceptions if needed
  52. if (null == value)
  53. value = "";
  54. // make sure startIndex is zero or positive
  55. if (startIndex < 0)
  56. throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex cannot be less than zero.");
  57. // make sure length is zero or positive
  58. if(length < 0)
  59. throw new System.ArgumentOutOfRangeException ("length", length, "Length cannot be less than zero.");
  60. if (capacity < 0)
  61. throw new System.ArgumentOutOfRangeException ("capacity", capacity, "capacity must be greater than zero.");
  62. // make sure startIndex and length give a valid substring of value
  63. // re-ordered to avoid possible integer overflow
  64. if (startIndex > value.Length - length)
  65. throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");
  66. if (capacity == 0)
  67. capacity = constDefaultCapacity;
  68. _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
  69. if (length > 0)
  70. String.InternalStrcpy(_str, 0, value, startIndex, length);
  71. _length = length;
  72. }
  73. public StringBuilder () : this (String.Empty, 0, 0, 0) {}
  74. public StringBuilder(int capacity) : this (String.Empty, 0, 0, capacity) {}
  75. public StringBuilder(int capacity, int maxCapacity) : this (String.Empty, 0, 0, capacity) {
  76. if (capacity > maxCapacity)
  77. throw new System.ArgumentOutOfRangeException ("capacity", "Capacity exceeds maximum capacity.");
  78. _maxCapacity = maxCapacity;
  79. }
  80. public StringBuilder( string value ) : this(value, 0, value == null ? 0 : value.Length, value == null? 0 : value.Length) {
  81. }
  82. public StringBuilder( string value, int capacity) : this(value, 0, value.Length, capacity) {}
  83. public int MaxCapacity {
  84. get {
  85. // MS runtime always returns Int32.MaxValue.
  86. return _maxCapacity;
  87. }
  88. }
  89. public int Capacity {
  90. get {
  91. return _str.Length;
  92. }
  93. set {
  94. if (value < _length)
  95. throw new ArgumentException( "Capacity must be larger than length" );
  96. InternalEnsureCapacity(value);
  97. }
  98. }
  99. public int Length {
  100. get {
  101. return _length;
  102. }
  103. set {
  104. if( value < 0 || value > _maxCapacity)
  105. throw new ArgumentOutOfRangeException();
  106. if (value == _length)
  107. return;
  108. if( value < _length )
  109. {
  110. // LAMESPEC: The spec is unclear as to what to do
  111. // with the capacity when truncating the string.
  112. // Do as MS, keep the capacity
  113. _length = value;
  114. } else
  115. {
  116. // Expand the capacity to the new length and
  117. // pad the string with spaces.
  118. // LAMESPEC: The spec says to put the spaces on the
  119. // left of the string however the MS implementation
  120. // puts them on the right. We'll do that for
  121. // compatibility (!)
  122. Append(' ', value - _length);
  123. }
  124. }
  125. }
  126. [IndexerName("Chars")]
  127. public char this [int index] {
  128. get {
  129. if (index >= _length || index < 0)
  130. throw new IndexOutOfRangeException();
  131. return _str [index];
  132. }
  133. set {
  134. if (index >= _length || index < 0)
  135. throw new IndexOutOfRangeException();
  136. if (null != _cached_str)
  137. InternalEnsureCapacity (_length);
  138. _str.InternalSetChar (index, value);
  139. }
  140. }
  141. public override string ToString ()
  142. {
  143. if (_length == 0)
  144. return String.Empty;
  145. if (null != _cached_str)
  146. return _cached_str;
  147. // If we only have a half-full buffer we return a new string.
  148. if (_length < (_str.Length >> 1))
  149. {
  150. _cached_str = _str.Substring(0, _length);
  151. return _cached_str;
  152. }
  153. _cached_str = _str;
  154. _str.InternalSetLength(_length);
  155. return _str;
  156. }
  157. public string ToString (int startIndex, int length)
  158. {
  159. // re-ordered to avoid possible integer overflow
  160. if (startIndex < 0 || length < 0 || startIndex > _length - length)
  161. throw new ArgumentOutOfRangeException();
  162. return _str.Substring (startIndex, length);
  163. }
  164. public int EnsureCapacity (int capacity)
  165. {
  166. if (capacity < 0)
  167. throw new ArgumentOutOfRangeException ("Capacity must be greater than 0." );
  168. if( capacity <= _str.Length )
  169. return _str.Length;
  170. InternalEnsureCapacity (capacity);
  171. return _str.Length;
  172. }
  173. public bool Equals (StringBuilder sb)
  174. {
  175. if (_length == sb.Length && _str == sb._str )
  176. return true;
  177. return false;
  178. }
  179. public StringBuilder Remove (int startIndex, int length)
  180. {
  181. // re-ordered to avoid possible integer overflow
  182. if (startIndex < 0 || length < 0 || startIndex > _length - length)
  183. throw new ArgumentOutOfRangeException();
  184. // Copy everything after the 'removed' part to the start
  185. // of the removed part and truncate the sLength
  186. if (_length - (startIndex + length) > 0)
  187. String.InternalStrcpy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
  188. _length -= length;
  189. if (null != _cached_str)
  190. InternalEnsureCapacity (_length);
  191. return this;
  192. }
  193. public StringBuilder Replace (char oldChar, char newChar)
  194. {
  195. return Replace( oldChar, newChar, 0, _length);
  196. }
  197. public StringBuilder Replace (char oldChar, char newChar, int startIndex, int count)
  198. {
  199. // re-ordered to avoid possible integer overflow
  200. if (startIndex > _length - count || startIndex < 0 || count < 0)
  201. throw new ArgumentOutOfRangeException();
  202. if (null != _cached_str)
  203. InternalEnsureCapacity (_str.Length);
  204. for (int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
  205. if( _str [replaceIterate] == oldChar )
  206. _str.InternalSetChar (replaceIterate, newChar);
  207. }
  208. return this;
  209. }
  210. public StringBuilder Replace( string oldValue, string newValue ) {
  211. return Replace (oldValue, newValue, 0, _length);
  212. }
  213. public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count )
  214. {
  215. if (oldValue == null)
  216. throw new ArgumentNullException ("The old value cannot be null.");
  217. if (startIndex < 0 || count < 0 || startIndex > _length - count)
  218. throw new ArgumentOutOfRangeException ();
  219. if (oldValue.Length == 0)
  220. throw new ArgumentException ("The old value cannot be zero length.");
  221. // TODO: OPTIMIZE!
  222. string replace = _str.Substring(startIndex, count).Replace(oldValue, newValue);
  223. InternalEnsureCapacity (replace.Length + (_length - count));
  224. String.InternalStrcpy (_str, startIndex, replace);
  225. _length = replace.Length + (_length - count);
  226. return this;
  227. }
  228. /* The Append Methods */
  229. public StringBuilder Append (char[] value)
  230. {
  231. if (value == null)
  232. return this;
  233. int needed_cap = _length + value.Length;
  234. if (null != _cached_str || _str.Length < needed_cap)
  235. InternalEnsureCapacity (needed_cap);
  236. String.InternalStrcpy (_str, _length, value);
  237. _length += value.Length;
  238. return this;
  239. }
  240. public StringBuilder Append (string value)
  241. {
  242. if (value == null)
  243. return this;
  244. int needed_cap = _length + value.Length;
  245. if (null != _cached_str || _str.Length < needed_cap)
  246. InternalEnsureCapacity (needed_cap);
  247. String.InternalStrcpy (_str, _length, value);
  248. _length += value.Length;
  249. return this;
  250. }
  251. public StringBuilder Append (bool value) {
  252. return Append (value.ToString());
  253. }
  254. public StringBuilder Append (byte value) {
  255. return Append (value.ToString());
  256. }
  257. public StringBuilder Append (decimal value) {
  258. return Append (value.ToString());
  259. }
  260. public StringBuilder Append (double value) {
  261. return Append (value.ToString());
  262. }
  263. public StringBuilder Append (short value) {
  264. return Append (value.ToString());
  265. }
  266. public StringBuilder Append (int value) {
  267. return Append (value.ToString());
  268. }
  269. public StringBuilder Append (long value) {
  270. return Append (value.ToString());
  271. }
  272. public StringBuilder Append (object value) {
  273. if (value == null)
  274. return this;
  275. return Append (value.ToString());
  276. }
  277. [CLSCompliant(false)]
  278. public StringBuilder Append (sbyte value) {
  279. return Append (value.ToString());
  280. }
  281. public StringBuilder Append (float value) {
  282. return Append (value.ToString());
  283. }
  284. [CLSCompliant(false)]
  285. public StringBuilder Append (ushort value) {
  286. return Append (value.ToString());
  287. }
  288. [CLSCompliant(false)]
  289. public StringBuilder Append (uint value) {
  290. return Append (value.ToString());
  291. }
  292. [CLSCompliant(false)]
  293. public StringBuilder Append (ulong value) {
  294. return Append (value.ToString());
  295. }
  296. public StringBuilder Append (char value)
  297. {
  298. int needed_cap = _length + 1;
  299. if (null != _cached_str || _str.Length < needed_cap)
  300. InternalEnsureCapacity (needed_cap);
  301. _str.InternalSetChar(_length, value);
  302. _length++;
  303. return this;
  304. }
  305. public StringBuilder Append (char value, int repeatCount)
  306. {
  307. if( repeatCount < 0 )
  308. throw new ArgumentOutOfRangeException();
  309. InternalEnsureCapacity (_length + repeatCount);
  310. for (int i = 0; i < repeatCount; i++)
  311. _str.InternalSetChar (_length++, value);
  312. return this;
  313. }
  314. public StringBuilder Append( char[] value, int startIndex, int charCount )
  315. {
  316. if (value == null) {
  317. if (!(startIndex == 0 && charCount == 0))
  318. throw new ArgumentNullException ("value");
  319. return this;
  320. }
  321. if ((charCount < 0 || startIndex < 0) || (startIndex > value.Length - charCount))
  322. throw new ArgumentOutOfRangeException();
  323. InternalEnsureCapacity (_length + charCount);
  324. String.InternalStrcpy (_str, _length, value, startIndex, charCount);
  325. _length += charCount;
  326. return this;
  327. }
  328. public StringBuilder Append (string value, int startIndex, int count)
  329. {
  330. if (value == null) {
  331. if (startIndex != 0 && count != 0)
  332. throw new ArgumentNullException ("value");
  333. return this;
  334. }
  335. if ((count < 0 || startIndex < 0) || (startIndex > value.Length - count))
  336. throw new ArgumentOutOfRangeException();
  337. int needed_cap = _length + count;
  338. if (null != _cached_str || _str.Length < needed_cap)
  339. InternalEnsureCapacity (needed_cap);
  340. String.InternalStrcpy (_str, _length, value, startIndex, count);
  341. _length += count;
  342. return this;
  343. }
  344. #if NET_2_0
  345. public StringBuilder AppendLine ()
  346. {
  347. return Append (System.Environment.NewLine);
  348. }
  349. public StringBuilder AppendLine (string value)
  350. {
  351. return Append (value).Append (System.Environment.NewLine);
  352. }
  353. #endif
  354. public StringBuilder AppendFormat (string format, object arg0)
  355. {
  356. return AppendFormat (null, format, new object [] { arg0 });
  357. }
  358. public StringBuilder AppendFormat (string format, params object[] args)
  359. {
  360. return AppendFormat (null, format, args);
  361. }
  362. public StringBuilder AppendFormat (IFormatProvider provider,
  363. string format,
  364. params object[] args)
  365. {
  366. String.FormatHelper (this, provider, format, args);
  367. return this;
  368. }
  369. public StringBuilder AppendFormat (string format, object arg0, object arg1)
  370. {
  371. return AppendFormat (null, format, new object [] { arg0, arg1 });
  372. }
  373. public StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
  374. {
  375. return AppendFormat (null, format, new object [] { arg0, arg1, arg2 });
  376. }
  377. /* The Insert Functions */
  378. public StringBuilder Insert (int index, char[] value)
  379. {
  380. return Insert (index, new string (value));
  381. }
  382. public StringBuilder Insert (int index, string value)
  383. {
  384. if( index > _length || index < 0)
  385. throw new ArgumentOutOfRangeException();
  386. if (value == null || value.Length == 0)
  387. return this;
  388. InternalEnsureCapacity (_length + value.Length);
  389. // Move everything to the right of the insert point across
  390. String.InternalStrcpy (_str, index + value.Length, _str, index, _length - index);
  391. // Copy in stuff from the insert buffer
  392. String.InternalStrcpy (_str, index, value);
  393. _length += value.Length;
  394. return this;
  395. }
  396. public StringBuilder Insert( int index, bool value ) {
  397. return Insert (index, value.ToString());
  398. }
  399. public StringBuilder Insert( int index, byte value ) {
  400. return Insert (index, value.ToString());
  401. }
  402. public StringBuilder Insert( int index, char value)
  403. {
  404. if (index > _length || index < 0)
  405. throw new ArgumentOutOfRangeException ("index");
  406. InternalEnsureCapacity (_length + 1);
  407. // Move everything to the right of the insert point across
  408. String.InternalStrcpy (_str, index + 1, _str, index, _length - index);
  409. _str.InternalSetChar (index, value);
  410. _length++;
  411. return this;
  412. }
  413. public StringBuilder Insert( int index, decimal value ) {
  414. return Insert (index, value.ToString());
  415. }
  416. public StringBuilder Insert( int index, double value ) {
  417. return Insert (index, value.ToString());
  418. }
  419. public StringBuilder Insert( int index, short value ) {
  420. return Insert (index, value.ToString());
  421. }
  422. public StringBuilder Insert( int index, int value ) {
  423. return Insert (index, value.ToString());
  424. }
  425. public StringBuilder Insert( int index, long value ) {
  426. return Insert (index, value.ToString());
  427. }
  428. public StringBuilder Insert( int index, object value ) {
  429. return Insert (index, value.ToString());
  430. }
  431. [CLSCompliant(false)]
  432. public StringBuilder Insert( int index, sbyte value ) {
  433. return Insert (index, value.ToString() );
  434. }
  435. public StringBuilder Insert (int index, float value) {
  436. return Insert (index, value.ToString() );
  437. }
  438. [CLSCompliant(false)]
  439. public StringBuilder Insert (int index, ushort value) {
  440. return Insert (index, value.ToString() );
  441. }
  442. [CLSCompliant(false)]
  443. public StringBuilder Insert (int index, uint value) {
  444. return Insert ( index, value.ToString() );
  445. }
  446. [CLSCompliant(false)]
  447. public StringBuilder Insert (int index, ulong value) {
  448. return Insert ( index, value.ToString() );
  449. }
  450. public StringBuilder Insert (int index, string value, int count)
  451. {
  452. // LAMESPEC: The spec says to throw an exception if
  453. // count < 0, while MS throws even for count < 1!
  454. if ( count < 0 )
  455. throw new ArgumentOutOfRangeException();
  456. if (value != null && value != String.Empty)
  457. for (int insertCount = 0; insertCount < count; insertCount++)
  458. Insert( index, value );
  459. return this;
  460. }
  461. public StringBuilder Insert (int index, char [] value, int startIndex, int charCount)
  462. {
  463. if (value == null) {
  464. if (startIndex == 0 && charCount == 0)
  465. return this;
  466. throw new ArgumentNullException ("value");
  467. }
  468. if (charCount < 0 || startIndex < 0 || startIndex > value.Length - charCount)
  469. throw new ArgumentOutOfRangeException ();
  470. return Insert (index, new String (value, startIndex, charCount));
  471. }
  472. private void InternalEnsureCapacity (int size)
  473. {
  474. if (size > _str.Length || _cached_str == _str)
  475. {
  476. int capacity = _str.Length;
  477. // Try double buffer, if that doesn't work, set the length as capacity
  478. if (size > capacity)
  479. {
  480. capacity = capacity << 1;
  481. if (size > capacity)
  482. capacity = size;
  483. if (capacity >= Int32.MaxValue || capacity < 0)
  484. capacity = Int32.MaxValue;
  485. }
  486. string tmp = String.InternalAllocateStr (capacity);
  487. if (_length > 0)
  488. String.InternalStrcpy (tmp, 0, _str, 0, _length);
  489. _str = tmp;
  490. }
  491. _cached_str = null;
  492. }
  493. }
  494. }