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