Add all missing sequential layout directives to corlib and system.
[mono.git] / mcs / class / corlib / System.Text / StringBuilder.cs
index 0f60ab857e62f10cfa1434472802f7d9aba34057..bfc8a8925e5db98a753853492dbdb5598a6b0016 100644 (file)
@@ -16,6 +16,7 @@
 
 //
 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright 2011 Xamarin Inc
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
+using System.Runtime.Serialization;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 
 namespace System.Text {
        
        [Serializable]
-       [MonoTODO ("Fix serialization compatibility with MS.NET")]
-       public sealed class StringBuilder 
+       [ComVisible (true)]
+        [MonoLimitation ("Serialization format not compatible with .NET")]
+       [StructLayout (LayoutKind.Sequential)]
+       public sealed class StringBuilder : ISerializable
        {
                private int _length;
-               private string _str = null;
-               private string _cached_str = null;
+               private string _str;
+               private string _cached_str;
                
-               private int _maxCapacity = Int32.MaxValue;
+               private int _maxCapacity;
                private const int constDefaultCapacity = 16;
 
                public StringBuilder(string value, int startIndex, int length, int capacity) 
+                       : this (value, startIndex, length, capacity, Int32.MaxValue)
+               {
+               }
+
+               private StringBuilder(string value, int startIndex, int length, int capacity, int maxCapacity)
                {
                        // first, check the parameters and throw appropriate exceptions if needed
                        if (null == value)
@@ -68,17 +78,28 @@ namespace System.Text {
                        if (capacity < 0)
                                throw new System.ArgumentOutOfRangeException ("capacity", capacity, "capacity must be greater than zero.");
 
+                       if (maxCapacity < 1)
+                               throw new System.ArgumentOutOfRangeException ("maxCapacity", "maxCapacity is less than one.");
+                       if (capacity > maxCapacity)
+                               throw new System.ArgumentOutOfRangeException ("capacity", "Capacity exceeds maximum capacity.");
+
                        // make sure startIndex and length give a valid substring of value
                        // re-ordered to avoid possible integer overflow
                        if (startIndex > value.Length - length)
                                throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");
 
-                       if (capacity == 0)
-                               capacity = constDefaultCapacity;
+                       if (capacity == 0) {
+                               if (maxCapacity > constDefaultCapacity)
+                                       capacity = constDefaultCapacity;
+                               else
+                                       _str = _cached_str = String.Empty;
+                       }
+                       _maxCapacity = maxCapacity;
 
-                       _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
+                       if (_str == null)
+                               _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
                        if (length > 0)
-                               String.InternalStrcpy(_str, 0, value, startIndex, length);
+                               String.CharCopy (_str, 0, value, startIndex, length);
                        
                        _length = length;
                }
@@ -87,29 +108,27 @@ namespace System.Text {
 
                public StringBuilder(int capacity) : this (String.Empty, 0, 0, capacity) {}
 
-               public StringBuilder(int capacity, int maxCapacity) : this (String.Empty, 0, 0, capacity) {
-                       if (maxCapacity < 1)
-                               throw new System.ArgumentOutOfRangeException ("maxCapacity", "maxCapacity is less than one.");
-                       if (capacity > maxCapacity)
-                               throw new System.ArgumentOutOfRangeException ("capacity", "Capacity exceeds maximum capacity.");
-
-                       _maxCapacity = maxCapacity;
-               }
+               public StringBuilder(int capacity, int maxCapacity) : this (String.Empty, 0, 0, capacity, maxCapacity) { }
 
                public StringBuilder (string value)
                {
+                       /*
+                        * This is an optimization to avoid allocating the internal string
+                        * until the first Append () call.
+                        * The runtime pinvoke marshalling code needs to be aware of this.
+                        */
                        if (null == value)
                                value = "";
                        
                        _length = value.Length;
                        _str = _cached_str = value;
+                       _maxCapacity = Int32.MaxValue;
                }
        
-               public StringBuilder( string value, int capacity) : this(value, 0, value.Length, capacity) {}
+               public StringBuilder( string value, int capacity) : this(value == null ? "" : value, 0, value == null ? 0 : value.Length, capacity) {}
        
                public int MaxCapacity {
                        get {
-                               // MS runtime always returns Int32.MaxValue.
                                return _maxCapacity;
                        }
                }
@@ -117,7 +136,7 @@ namespace System.Text {
                public int Capacity {
                        get {
                                if (_str.Length == 0)
-                                       return constDefaultCapacity;
+                                       return Math.Min (_maxCapacity, constDefaultCapacity);
                                
                                return _str.Length;
                        }
@@ -126,6 +145,9 @@ namespace System.Text {
                                if (value < _length)
                                        throw new ArgumentException( "Capacity must be larger than length" );
 
+                               if (value > _maxCapacity)
+                                       throw new ArgumentOutOfRangeException ("value", "Should be less than or equal to MaxCapacity");
+
                                InternalEnsureCapacity(value);
                        }
                }
@@ -153,13 +175,8 @@ namespace System.Text {
                                        _length = value;
                                } else {
                                        // Expand the capacity to the new length and
-                                       // pad the string with spaces.
-                                       
-                                       // LAMESPEC: The spec says to put the spaces on the
-                                       // left of the string however the MS implementation
-                                       // puts them on the right.  We'll do that for 
-                                       // compatibility (!)
-                                       Append(' ', value - _length);
+                                       // pad the string with NULL characters.
+                                       Append('\0', value - _length);
                                }
                        }
                }
@@ -193,9 +210,11 @@ namespace System.Text {
                                return _cached_str;
 
                        // If we only have a half-full buffer we return a new string.
-                       if (_length < (_str.Length >> 1)
+                       if (_length < (_str.Length >> 1) || (_str.Length > string.LOS_limit && _length <= string.LOS_limit))
                        {
-                               _cached_str = _str.Substring(0, _length);
+                               // use String.SubstringUnchecked instead of String.Substring
+                               // as the former is guaranteed to create a new string object
+                               _cached_str = _str.SubstringUnchecked (0, _length);
                                return _cached_str;
                        }
 
@@ -211,7 +230,12 @@ namespace System.Text {
                        if (startIndex < 0 || length < 0 || startIndex > _length - length)
                                throw new ArgumentOutOfRangeException();
 
-                       return _str.Substring (startIndex, length);
+                       // use String.SubstringUnchecked instead of String.Substring
+                       // as the former is guaranteed to create a new string object
+                       if (startIndex == 0 && length == _length)
+                               return ToString ();
+                       else
+                               return _str.SubstringUnchecked (startIndex, length);
                }
 
                public int EnsureCapacity (int capacity) 
@@ -229,6 +253,9 @@ namespace System.Text {
 
                public bool Equals (StringBuilder sb) 
                {
+                       if (((object)sb) == null)
+                               return false;
+                       
                        if (_length == sb.Length && _str == sb._str )
                                return true;
 
@@ -247,7 +274,7 @@ namespace System.Text {
                        // Copy everything after the 'removed' part to the start 
                        // of the removed part and truncate the sLength
                        if (_length - (startIndex + length) > 0)
-                               String.InternalStrcpy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
+                               String.CharCopy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
 
                        _length -= length;
 
@@ -291,12 +318,22 @@ namespace System.Text {
                        if (oldValue.Length == 0)
                                throw new ArgumentException ("The old value cannot be zero length.");
 
-                       // TODO: OPTIMIZE!
-                       string replace = _str.Substring(startIndex, count).Replace(oldValue, newValue);
+                       string substr = _str.Substring(startIndex, count);
+                       string replace = substr.Replace(oldValue, newValue);
+                       // return early if no oldValue was found
+                       if ((object) replace == (object) substr)
+                               return this;
 
                        InternalEnsureCapacity (replace.Length + (_length - count));
 
-                       String.InternalStrcpy (_str, startIndex, replace);
+                       // shift end part
+                       if (replace.Length < count)
+                               String.CharCopy (_str, startIndex + replace.Length, _str, startIndex + count, _length - startIndex  - count);
+                       else if (replace.Length > count)
+                               String.CharCopyReverse (_str, startIndex + replace.Length, _str, startIndex + count, _length - startIndex  - count);
+
+                       // copy middle part back into _str
+                       String.CharCopy (_str, startIndex, replace, 0, replace.Length);
                        
                        _length = replace.Length + (_length - count);
 
@@ -314,8 +351,8 @@ namespace System.Text {
                        if (null != _cached_str || _str.Length < needed_cap)
                                InternalEnsureCapacity (needed_cap);
                        
-                       String.InternalStrcpy (_str, _length, value);
-                       _length += value.Length;
+                       String.CharCopy (_str, _length, value, 0, value.Length);
+                       _length = needed_cap;
 
                        return this;
                } 
@@ -335,8 +372,8 @@ namespace System.Text {
                        if (null != _cached_str || _str.Length < needed_cap)
                                InternalEnsureCapacity (needed_cap);
 
-                       String.InternalStrcpy (_str, _length, value);
-                       _length += value.Length;
+                       String.CharCopy (_str, _length, value, 0, value.Length);
+                       _length = needed_cap;
                        return this;
                }
 
@@ -406,7 +443,7 @@ namespace System.Text {
                                InternalEnsureCapacity (needed_cap);
 
                        _str.InternalSetChar(_length, value);
-                       _length++;
+                       _length = needed_cap;
 
                        return this;
                }
@@ -436,11 +473,11 @@ namespace System.Text {
                        if ((charCount < 0 || startIndex < 0) || (startIndex > value.Length - charCount)) 
                                throw new ArgumentOutOfRangeException();
                        
-                       
-                       InternalEnsureCapacity (_length + charCount);
+                       int needed_cap = _length + charCount;
+                       InternalEnsureCapacity (needed_cap);
 
-                       String.InternalStrcpy (_str, _length, value, startIndex, charCount);
-                       _length += charCount;
+                       String.CharCopy (_str, _length, value, startIndex, charCount);
+                       _length = needed_cap;
 
                        return this;
                }
@@ -461,29 +498,32 @@ namespace System.Text {
                        if (null != _cached_str || _str.Length < needed_cap)
                                InternalEnsureCapacity (needed_cap);
 
-                       String.InternalStrcpy (_str, _length, value, startIndex, count);
+                       String.CharCopy (_str, _length, value, startIndex, count);
                        
-                       _length += count;
+                       _length = needed_cap;
 
                        return this;
                }
 
-#if NET_2_0
+#if NET_4_0 || MOONLIGHT || MOBILE
+               public StringBuilder Clear ()
+               {
+                       Length = 0;
+                       return this;
+               }
+#endif
+
+               [ComVisible (false)]
                public StringBuilder AppendLine ()
                {
                        return Append (System.Environment.NewLine);
                }
 
+               [ComVisible (false)]
                public StringBuilder AppendLine (string value)
                {
                        return Append (value).Append (System.Environment.NewLine);
                }
-#endif
-
-               public StringBuilder AppendFormat (string format, object arg0)
-               {
-                       return AppendFormat (null, format, new object [] { arg0 });
-               }
 
                public StringBuilder AppendFormat (string format, params object[] args)
                {
@@ -498,12 +538,32 @@ namespace System.Text {
                        return this;
                }
 
-               public StringBuilder AppendFormat (string format, object arg0, object arg1)
+#if MOONLIGHT
+               internal
+#else
+               public
+#endif
+               StringBuilder AppendFormat (string format, object arg0)
+               {
+                       return AppendFormat (null, format, new object [] { arg0 });
+               }
+
+#if MOONLIGHT
+               internal
+#else
+               public
+#endif
+               StringBuilder AppendFormat (string format, object arg0, object arg1)
                {
                        return AppendFormat (null, format, new object [] { arg0, arg1 });
                }
 
-               public StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
+#if MOONLIGHT
+               internal
+#else
+               public
+#endif
+               StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
                {
                        return AppendFormat (null, format, new object [] { arg0, arg1, arg2 });
                }
@@ -526,10 +586,10 @@ namespace System.Text {
                        InternalEnsureCapacity (_length + value.Length);
 
                        // Move everything to the right of the insert point across
-                       String.InternalStrcpy (_str, index + value.Length, _str, index, _length - index);
+                       String.CharCopyReverse (_str, index + value.Length, _str, index, _length - index);
                        
                        // Copy in stuff from the insert buffer
-                       String.InternalStrcpy (_str, index, value);
+                       String.CharCopy (_str, index, value, 0, value.Length);
                        
                        _length += value.Length;
 
@@ -552,7 +612,7 @@ namespace System.Text {
                        InternalEnsureCapacity (_length + 1);
                        
                        // Move everything to the right of the insert point across
-                       String.InternalStrcpy (_str, index + 1, _str, index, _length - index);
+                       String.CharCopyReverse (_str, index + 1, _str, index, _length - index);
                        
                        _str.InternalSetChar (index, value);
                        _length++;
@@ -639,18 +699,16 @@ namespace System.Text {
        
                private void InternalEnsureCapacity (int size) 
                {
-                       if (size > _str.Length || _cached_str == _str) 
-                       {
+                       if (size > _str.Length || (object) _cached_str == (object) _str) {
                                int capacity = _str.Length;
 
                                // Try double buffer, if that doesn't work, set the length as capacity
-                               if (size > capacity) 
-                               {
+                               if (size > capacity) {
                                        
                                        // The first time a string is appended, we just set _cached_str
                                        // and _str to it. This allows us to do some optimizations.
                                        // Below, we take this into account.
-                                       if (_cached_str == _str && capacity < constDefaultCapacity)
+                                       if ((object) _cached_str == (object) _str && capacity < constDefaultCapacity)
                                                capacity = constDefaultCapacity;
                                        
                                        capacity = capacity << 1;
@@ -660,18 +718,57 @@ namespace System.Text {
                                        if (capacity >= Int32.MaxValue || capacity < 0)
                                                capacity = Int32.MaxValue;
 
+                                       if (capacity > _maxCapacity && size <= _maxCapacity)
+                                               capacity = _maxCapacity;
+                                       
                                        if (capacity > _maxCapacity)
                                                throw new ArgumentOutOfRangeException ("size", "capacity was less than the current size.");
                                }
 
                                string tmp = String.InternalAllocateStr (capacity);
                                if (_length > 0)
-                                       String.InternalStrcpy (tmp, 0, _str, 0, _length);
+                                       String.CharCopy (tmp, 0, _str, 0, _length);
 
                                _str = tmp;
                        }
 
                        _cached_str = null;
                }
+
+               [ComVisible (false)]
+               public void CopyTo (int sourceIndex, char [] destination, int destinationIndex, int count)
+               {
+                       if (destination == null)
+                               throw new ArgumentNullException ("destination");
+                       if ((Length - count < sourceIndex) ||
+                           (destination.Length -count < destinationIndex) ||
+                           (sourceIndex < 0 || destinationIndex < 0 || count < 0))
+                               throw new ArgumentOutOfRangeException ();
+
+                       for (int i = 0; i < count; i++)
+                               destination [destinationIndex+i] = _str [sourceIndex+i];
+               }
+
+               void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
+               {
+                       info.AddValue ("m_MaxCapacity", _maxCapacity);
+                       info.AddValue ("Capacity", Capacity);
+                       info.AddValue ("m_StringValue", ToString ());
+                       info.AddValue ("m_currentThread", 0);
+               }
+
+               StringBuilder (SerializationInfo info, StreamingContext context)
+               {
+                       string s = info.GetString ("m_StringValue");
+                       if (s == null)
+                               s = "";
+                       _length = s.Length;
+                       _str = _cached_str = s;
+                       
+                       _maxCapacity = info.GetInt32 ("m_MaxCapacity");
+                       if (_maxCapacity < 0)
+                               _maxCapacity = Int32.MaxValue;
+                       Capacity = info.GetInt32 ("Capacity");
+               }
        }
 }