3d88cd08ff0d438859c3f6486085378b47256c81
[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                                 if (value == capacity)
157                                         return; // LAMENESS: see MemoryStreamTest.ConstructorFive
158
159                                 if (!expandable)
160                                         throw new NotSupportedException ("Cannot expand this MemoryStream");
161
162                                 if (value < 0 || value < length)
163                                         throw new ArgumentOutOfRangeException ("value",
164                                         "New capacity cannot be negative or less than the current capacity " + value + " " + capacity);
165
166                                 byte [] newBuffer = null;
167                                 if (value != 0) {
168                                         newBuffer = new byte [value];
169                                         Buffer.BlockCopy (internalBuffer, 0, newBuffer, 0, length);
170                                 }
171
172                                 dirty_bytes = 0; // discard any dirty area beyond previous length
173                                 internalBuffer = newBuffer; // It's null when capacity is set to 0
174                                 capacity = value;
175                         }
176                 }
177
178                 public override long Length {
179                         get {
180                                 // LAMESPEC: The spec says to throw an IOException if the
181                                 // stream is closed and an ObjectDisposedException if
182                                 // "methods were called after the stream was closed".  What
183                                 // is the difference?
184
185                                 CheckIfClosedThrowDisposed ();
186
187                                 // This is ok for MemoryStreamTest.ConstructorFive
188                                 return length - initialIndex;
189                         }
190                 }
191
192                 public override long Position {
193                         get {
194                                 CheckIfClosedThrowDisposed ();
195                                 return position - initialIndex;
196                         }
197
198                         set {
199                                 CheckIfClosedThrowDisposed ();
200                                 if (value < 0)
201                                         throw new ArgumentOutOfRangeException ("value",
202                                                                 "Position cannot be negative" );
203
204                                 if (value > Int32.MaxValue)
205                                         throw new ArgumentOutOfRangeException ("value",
206                                         "Position must be non-negative and less than 2^31 - 1 - origin");
207
208                                 position = initialIndex + (int) value;
209                         }
210                 }
211
212                 protected override void Dispose (bool disposing)
213                 {
214                         streamClosed = true;
215                         expandable = false;
216                 }
217
218                 public override void Flush ()
219                 {
220                         // Do nothing
221                 }
222
223                 public virtual byte [] GetBuffer ()
224                 {
225                         if (!allowGetBuffer)
226                                 throw new UnauthorizedAccessException ();
227
228                         return internalBuffer;
229                 }
230
231                 public override int Read ([In,Out] byte [] buffer, int offset, int count)
232                 {
233                         CheckIfClosedThrowDisposed ();
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                         if (position >= length || count == 0)
246                                 return 0;
247
248                         if (position > length - count)
249                                 count = length - position;
250
251                         Buffer.BlockCopy (internalBuffer, position, buffer, offset, count);
252                         position += count;
253                         return count;
254                 }
255
256                 public override int ReadByte ()
257                 {
258                         CheckIfClosedThrowDisposed ();
259                         if (position >= length)
260                                 return -1;
261
262                         return internalBuffer [position++];
263                 }
264
265                 public override long Seek (long offset, SeekOrigin loc)
266                 {
267                         CheckIfClosedThrowDisposed ();
268
269                         // It's funny that they don't throw this exception for < Int32.MinValue
270                         if (offset > (long) Int32.MaxValue)
271                                 throw new ArgumentOutOfRangeException ("Offset out of range. " + offset);
272
273                         int refPoint;
274                         switch (loc) {
275                         case SeekOrigin.Begin:
276                                 if (offset < 0)
277                                         throw new IOException ("Attempted to seek before start of MemoryStream.");
278                                 refPoint = initialIndex;
279                                 break;
280                         case SeekOrigin.Current:
281                                 refPoint = position;
282                                 break;
283                         case SeekOrigin.End:
284                                 refPoint = length;
285                                 break;
286                         default:
287                                 throw new ArgumentException ("loc", "Invalid SeekOrigin");
288                         }
289
290                         // LAMESPEC: My goodness, how may LAMESPECs are there in this
291                         // class! :)  In the spec for the Position property it's stated
292                         // "The position must not be more than one byte beyond the end of the stream."
293                         // In the spec for seek it says "Seeking to any location beyond the length of the 
294                         // stream is supported."  That's a contradiction i'd say.
295                         // I guess seek can go anywhere but if you use position it may get moved back.
296
297                         refPoint += (int) offset;
298                         if (refPoint < initialIndex)
299                                 throw new IOException ("Attempted to seek before start of MemoryStream.");
300
301                         position = refPoint;
302                         return position;
303                 }
304
305                 int CalculateNewCapacity (int minimum)
306                 {
307                         if (minimum < 256)
308                                 minimum = 256; // See GetBufferTwo test
309
310                         if (minimum < capacity * 2)
311                                 minimum = capacity * 2;
312
313                         return minimum;
314                 }
315
316                 void Expand (int newSize)
317                 {
318                         // We don't need to take into account the dirty bytes when incrementing the
319                         // Capacity, as changing it will only preserve the valid clear region.
320                         if (newSize > capacity)
321                                 Capacity = CalculateNewCapacity (newSize);
322                         else if (dirty_bytes > 0) {
323                                 Array.Clear (internalBuffer, length, dirty_bytes);
324                                 dirty_bytes = 0;
325                         }
326                 }
327
328                 public override void SetLength (long value)
329                 {
330                         if (!expandable && value > capacity)
331                                 throw new NotSupportedException ("Expanding this MemoryStream is not supported");
332
333                         CheckIfClosedThrowDisposed ();
334
335                         if (!canWrite) {
336                                 throw new NotSupportedException (Locale.GetText 
337                                         ("Cannot write to this MemoryStream"));
338                         }
339
340                         // LAMESPEC: AGAIN! It says to throw this exception if value is
341                         // greater than "the maximum length of the MemoryStream".  I haven't
342                         // seen anywhere mention what the maximum length of a MemoryStream is and
343                         // since we're this far this memory stream is expandable.
344                         if (value < 0 || (value + initialIndex) > (long) Int32.MaxValue)
345                                 throw new ArgumentOutOfRangeException ();
346
347                         int newSize = (int) value + initialIndex;
348
349                         if (newSize > length)
350                                 Expand (newSize);
351                         else if (newSize < length) // Postpone the call to Array.Clear till expand time
352                                 dirty_bytes += length - newSize;
353
354                         length = newSize;
355                         if (position > length)
356                                 position = length;
357                 }
358
359                 public virtual byte [] ToArray ()
360                 {
361                         int l = length - initialIndex;
362                         byte[] outBuffer = new byte [l];
363
364                         if (internalBuffer != null)
365                                 Buffer.BlockCopy (internalBuffer, initialIndex, outBuffer, 0, l);
366                         return outBuffer; 
367                 }
368
369                 public override void Write (byte [] buffer, int offset, int count)
370                 {
371                         CheckIfClosedThrowDisposed ();
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                         // reordered to avoid possible integer overflow
387                         if (position > length - count)
388                                 Expand (position + count);
389
390                         Buffer.BlockCopy (buffer, offset, internalBuffer, position, count);
391                         position += count;
392                         if (position >= length)
393                                 length = position;
394                 }
395
396                 public override void WriteByte (byte value)
397                 {
398                         CheckIfClosedThrowDisposed ();
399                         if (!canWrite)
400                                 throw new NotSupportedException ("Cannot write to this stream.");
401
402                         if (position >= length) {
403                                 Expand (position + 1);
404                                 length = position + 1;
405                         }
406
407                         internalBuffer [position++] = value;
408                 }
409
410                 public virtual void WriteTo (Stream stream)
411                 {
412                         CheckIfClosedThrowDisposed ();
413
414                         if (stream == null)
415                                 throw new ArgumentNullException ("stream");
416
417                         stream.Write (internalBuffer, initialIndex, length - initialIndex);
418                 }
419
420 #if NET_4_0
421                 public override void ObjectInvariant ()
422                 {
423                 }
424 #endif
425         }               
426 }