Make MemoryStream more .net compatible
[mono.git] / mcs / class / corlib / System.IO / MemoryStream.cs
1 //
2 // System.IO.MemoryStream.cs
3 //
4 // Authors:     Marcin Szczepanski (marcins@zipworld.com.au)
5 //              Patrik Torstensson
6 //              Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (c) 2001,2002 Marcin Szczepanski, Patrik Torstensson
9 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
10 // Copyright (C) 2004 Novell (http://www.novell.com)
11 //
12
13 //
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System.Globalization;
37 using System.Runtime.InteropServices;
38
39 namespace System.IO
40 {
41         [Serializable]
42         [ComVisible (true)]
43         [MonoLimitation ("Serialization format not compatible with .NET")]
44         public class MemoryStream : Stream
45         {
46                 bool canWrite;
47                 bool allowGetBuffer;
48                 int capacity;
49                 int length;
50                 byte [] internalBuffer;
51                 int initialIndex;
52                 bool expandable;
53                 bool streamClosed;
54                 int position;
55                 int dirty_bytes;
56
57                 public MemoryStream () : this (0)
58                 {
59                 }
60
61                 public MemoryStream (int capacity)
62                 {
63                         if (capacity < 0)
64                                 throw new ArgumentOutOfRangeException ("capacity");
65
66                         canWrite = true;
67
68                         this.capacity = capacity;
69                         internalBuffer = new byte [capacity];
70
71                         expandable = true;
72                         allowGetBuffer = true;
73                 }
74
75                 public MemoryStream (byte [] buffer)
76                 {
77                         if (buffer == null)
78                                 throw new ArgumentNullException ("buffer");
79                         
80                         InternalConstructor (buffer, 0, buffer.Length, true, false);                        
81                 }
82
83                 public MemoryStream (byte [] buffer, bool writable)
84                 {
85                         if (buffer == null)
86                                 throw new ArgumentNullException ("buffer");
87                         
88                         InternalConstructor (buffer, 0, buffer.Length, writable, false);
89                 }
90
91                 public MemoryStream (byte [] buffer, int index, int count)
92                 {
93                         InternalConstructor (buffer, index, count, true, false);
94                 }
95
96                 public MemoryStream (byte [] buffer, int index, int count, bool writable)
97                 {
98                         InternalConstructor (buffer, index, count, writable, false);
99                 }
100
101                 public MemoryStream (byte [] buffer, int index, int count, bool writable, bool publiclyVisible)
102                 {
103                         InternalConstructor (buffer, index, count, writable, publiclyVisible);
104                 }
105
106                 void InternalConstructor (byte [] buffer, int index, int count, bool writable, bool publicallyVisible)
107                 {
108                         if (buffer == null)
109                                 throw new ArgumentNullException ("buffer");
110
111                         if (index < 0 || count < 0)
112                                 throw new ArgumentOutOfRangeException ("index or count is less than 0.");
113
114                         if (buffer.Length - index < count)
115                                 throw new ArgumentException ("index+count", 
116                                                              "The size of the buffer is less than index + count.");
117
118                         canWrite = writable;
119
120                         internalBuffer = buffer;
121                         capacity = count + index;
122                         length = capacity;
123                         position = index;
124                         initialIndex = index;
125
126                         allowGetBuffer = publicallyVisible;
127                         expandable = false;                
128                 }
129
130                 void CheckIfClosedThrowDisposed ()
131                 {
132                         if (streamClosed)
133                                 throw new ObjectDisposedException ("MemoryStream");
134                 }
135                 
136                 public override bool CanRead {
137                         get { return !streamClosed; }
138                 }
139
140                 public override bool CanSeek {
141                         get { return !streamClosed; }
142                 }
143
144                 public override bool CanWrite {
145                         get { return (!streamClosed && canWrite); }
146                 }
147
148                 public virtual int Capacity {
149                         get {
150                                 CheckIfClosedThrowDisposed ();
151                                 return capacity - initialIndex;
152                         }
153
154                         set {
155                                 CheckIfClosedThrowDisposed ();
156
157                                 if (!expandable)
158                                         throw new NotSupportedException ("Cannot expand this MemoryStream");
159
160                                 if (value < 0 || value < length)
161                                         throw new ArgumentOutOfRangeException ("value",
162                                         "New capacity cannot be negative or less than the current capacity " + value + " " + capacity);
163
164                                 if (internalBuffer != null && value == internalBuffer.Length)
165                                         return;
166
167                                 byte [] newBuffer = null;
168                                 if (value != 0) {
169                                         newBuffer = new byte [value];
170                                         if (internalBuffer != null)
171                                                 Buffer.BlockCopy (internalBuffer, 0, newBuffer, 0, length);
172                                 }
173
174                                 dirty_bytes = 0; // discard any dirty area beyond previous length
175                                 internalBuffer = newBuffer; // It's null when capacity is set to 0
176                                 capacity = value;
177                         }
178                 }
179
180                 public override long Length {
181                         get {
182                                 // LAMESPEC: The spec says to throw an IOException if the
183                                 // stream is closed and an ObjectDisposedException if
184                                 // "methods were called after the stream was closed".  What
185                                 // is the difference?
186
187                                 CheckIfClosedThrowDisposed ();
188
189                                 // This is ok for MemoryStreamTest.ConstructorFive
190                                 return length - initialIndex;
191                         }
192                 }
193
194                 public override long Position {
195                         get {
196                                 CheckIfClosedThrowDisposed ();
197                                 return position - initialIndex;
198                         }
199
200                         set {
201                                 CheckIfClosedThrowDisposed ();
202                                 if (value < 0)
203                                         throw new ArgumentOutOfRangeException ("value",
204                                                                 "Position cannot be negative" );
205
206                                 if (value > Int32.MaxValue)
207                                         throw new ArgumentOutOfRangeException ("value",
208                                         "Position must be non-negative and less than 2^31 - 1 - origin");
209
210                                 position = initialIndex + (int) value;
211                         }
212                 }
213
214                 protected override void Dispose (bool disposing)
215                 {
216                         streamClosed = true;
217                         expandable = false;
218                 }
219
220                 public override void Flush ()
221                 {
222                         // Do nothing
223                 }
224
225                 public virtual byte [] GetBuffer ()
226                 {
227                         if (!allowGetBuffer)
228                                 throw new UnauthorizedAccessException ();
229
230                         return internalBuffer;
231                 }
232
233                 public override int Read ([In,Out] byte [] buffer, int offset, int count)
234                 {
235                         if (buffer == null)
236                                 throw new ArgumentNullException ("buffer");
237
238                         if (offset < 0 || count < 0)
239                                 throw new ArgumentOutOfRangeException ("offset or count less than zero.");
240
241                         if (buffer.Length - offset < count )
242                                 throw new ArgumentException ("offset+count",
243                                                               "The size of the buffer is less than offset + count.");
244
245                         CheckIfClosedThrowDisposed ();
246
247                         if (position >= length || count == 0)
248                                 return 0;
249
250                         if (position > length - count)
251                                 count = length - position;
252
253                         Buffer.BlockCopy (internalBuffer, position, buffer, offset, count);
254                         position += count;
255                         return count;
256                 }
257
258                 public override int ReadByte ()
259                 {
260                         CheckIfClosedThrowDisposed ();
261                         if (position >= length)
262                                 return -1;
263
264                         return internalBuffer [position++];
265                 }
266
267                 public override long Seek (long offset, SeekOrigin loc)
268                 {
269                         CheckIfClosedThrowDisposed ();
270
271                         // It's funny that they don't throw this exception for < Int32.MinValue
272                         if (offset > (long) Int32.MaxValue)
273                                 throw new ArgumentOutOfRangeException ("Offset out of range. " + offset);
274
275                         int refPoint;
276                         switch (loc) {
277                         case SeekOrigin.Begin:
278                                 if (offset < 0)
279                                         throw new IOException ("Attempted to seek before start of MemoryStream.");
280                                 refPoint = initialIndex;
281                                 break;
282                         case SeekOrigin.Current:
283                                 refPoint = position;
284                                 break;
285                         case SeekOrigin.End:
286                                 refPoint = length;
287                                 break;
288                         default:
289                                 throw new ArgumentException ("loc", "Invalid SeekOrigin");
290                         }
291
292                         // LAMESPEC: My goodness, how may LAMESPECs are there in this
293                         // class! :)  In the spec for the Position property it's stated
294                         // "The position must not be more than one byte beyond the end of the stream."
295                         // In the spec for seek it says "Seeking to any location beyond the length of the 
296                         // stream is supported."  That's a contradiction i'd say.
297                         // I guess seek can go anywhere but if you use position it may get moved back.
298
299                         refPoint += (int) offset;
300                         if (refPoint < initialIndex)
301                                 throw new IOException ("Attempted to seek before start of MemoryStream.");
302
303                         position = refPoint;
304                         return position;
305                 }
306
307                 int CalculateNewCapacity (int minimum)
308                 {
309                         if (minimum < 256)
310                                 minimum = 256; // See GetBufferTwo test
311
312                         if (minimum < capacity * 2)
313                                 minimum = capacity * 2;
314
315                         return minimum;
316                 }
317
318                 void Expand (int newSize)
319                 {
320                         // We don't need to take into account the dirty bytes when incrementing the
321                         // Capacity, as changing it will only preserve the valid clear region.
322                         if (newSize > capacity)
323                                 Capacity = CalculateNewCapacity (newSize);
324                         else if (dirty_bytes > 0) {
325                                 Array.Clear (internalBuffer, length, dirty_bytes);
326                                 dirty_bytes = 0;
327                         }
328                 }
329
330                 public override void SetLength (long value)
331                 {
332                         if (!expandable && value > capacity)
333                                 throw new NotSupportedException ("Expanding this MemoryStream is not supported");
334
335                         CheckIfClosedThrowDisposed ();
336
337                         if (!canWrite) {
338                                 throw new NotSupportedException (Locale.GetText 
339                                         ("Cannot write to this MemoryStream"));
340                         }
341
342                         // LAMESPEC: AGAIN! It says to throw this exception if value is
343                         // greater than "the maximum length of the MemoryStream".  I haven't
344                         // seen anywhere mention what the maximum length of a MemoryStream is and
345                         // since we're this far this memory stream is expandable.
346                         if (value < 0 || (value + initialIndex) > (long) Int32.MaxValue)
347                                 throw new ArgumentOutOfRangeException ();
348
349                         int newSize = (int) value + initialIndex;
350
351                         if (newSize > length)
352                                 Expand (newSize);
353                         else if (newSize < length) // Postpone the call to Array.Clear till expand time
354                                 dirty_bytes += length - newSize;
355
356                         length = newSize;
357                         if (position > length)
358                                 position = length;
359                 }
360
361                 public virtual byte [] ToArray ()
362                 {
363                         int l = length - initialIndex;
364                         byte[] outBuffer = new byte [l];
365
366                         if (internalBuffer != null)
367                                 Buffer.BlockCopy (internalBuffer, initialIndex, outBuffer, 0, l);
368                         return outBuffer; 
369                 }
370
371                 public override void Write (byte [] buffer, int offset, int count)
372                 {
373                         if (!canWrite)
374                                 throw new NotSupportedException ("Cannot write to this stream.");
375
376                         if (buffer == null)
377                                 throw new ArgumentNullException ("buffer");
378                         
379                         if (offset < 0 || count < 0)
380                                 throw new ArgumentOutOfRangeException ();
381
382                         if (buffer.Length - offset < count)
383                                 throw new ArgumentException ("offset+count",
384                                                              "The size of the buffer is less than offset + count.");
385
386                         CheckIfClosedThrowDisposed ();
387
388                         // reordered to avoid possible integer overflow
389                         if (position > length - count)
390                                 Expand (position + count);
391
392                         Buffer.BlockCopy (buffer, offset, internalBuffer, position, count);
393                         position += count;
394                         if (position >= length)
395                                 length = position;
396                 }
397
398                 public override void WriteByte (byte value)
399                 {
400                         CheckIfClosedThrowDisposed ();
401                         if (!canWrite)
402                                 throw new NotSupportedException ("Cannot write to this stream.");
403
404                         if (position >= length) {
405                                 Expand (position + 1);
406                                 length = position + 1;
407                         }
408
409                         internalBuffer [position++] = value;
410                 }
411
412                 public virtual void WriteTo (Stream stream)
413                 {
414                         CheckIfClosedThrowDisposed ();
415
416                         if (stream == null)
417                                 throw new ArgumentNullException ("stream");
418
419                         stream.Write (internalBuffer, initialIndex, length - initialIndex);
420                 }
421
422 #if NET_4_0
423                 protected override void ObjectInvariant ()
424                 {
425                 }
426 #endif
427         }               
428 }