* File.cs: Changed argument names and thrown exception to better match
[mono.git] / mcs / class / corlib / System.IO / MemoryStream.cs
1 //
2 // System.IO.MemoryStream 
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 #if NET_2_0
43         [ComVisible (true)]
44 #endif
45         [MonoTODO ("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
58                 public MemoryStream () : this (0)
59                 {
60                 }
61
62                 public MemoryStream (int capacity)
63                 {
64                         if (capacity < 0)
65                                 throw new ArgumentOutOfRangeException ("capacity");
66
67                         canWrite = true;
68
69                         this.capacity = capacity;
70                         internalBuffer = new byte [capacity];
71
72                         expandable = true;
73                         allowGetBuffer = true;
74                 }
75
76                 public MemoryStream (byte [] buffer)
77                 {
78                         if (buffer == null)
79                                 throw new ArgumentNullException ("buffer");
80                         
81                         InternalConstructor (buffer, 0, buffer.Length, true, false);                        
82                 }
83
84                 public MemoryStream (byte [] buffer, bool writeable)
85                 {
86                         if (buffer == null)
87                                 throw new ArgumentNullException ("buffer");
88                         
89                         InternalConstructor (buffer, 0, buffer.Length, writeable, false);
90                 }
91
92                 public MemoryStream (byte [] buffer, int index, int count)
93                 {
94                         InternalConstructor (buffer, index, count, true, false);
95                 }
96
97                 public MemoryStream (byte [] buffer, int index, int count, bool writeable)
98                 {
99                         InternalConstructor (buffer, index, count, writeable, false);
100                 }
101
102                 public MemoryStream (byte [] buffer, int index, int count, bool writeable, bool publicallyVisible)
103                 {
104                         InternalConstructor (buffer, index, count, writeable, publicallyVisible);
105                 }
106
107                 void InternalConstructor (byte [] buffer, int index, int count, bool writeable, bool publicallyVisible)
108                 {
109                         if (buffer == null)
110                                 throw new ArgumentNullException ("buffer");
111
112                         if (index < 0 || count < 0)
113                                 throw new ArgumentOutOfRangeException ("index or count is less than 0.");
114
115                         if (buffer.Length - index < count)
116                                 throw new ArgumentException ("index+count", 
117                                                              "The size of the buffer is less than index + count.");
118
119                         canWrite = writeable;
120
121                         internalBuffer = buffer;
122                         capacity = count + index;
123                         length = capacity;
124                         position = index;
125                         initialIndex = index;
126
127                         allowGetBuffer = publicallyVisible;
128                         expandable = false;                
129                 }
130
131                 void CheckIfClosedThrowDisposed ()
132                 {
133                         if (streamClosed)
134                                 throw new ObjectDisposedException ("MemoryStream");
135                 }
136                 
137                 public override bool CanRead {
138                         get { return !streamClosed; }
139                 }
140
141                 public override bool CanSeek {
142                         get { return !streamClosed; }
143                 }
144
145                 public override bool CanWrite {
146                         get { return (!streamClosed && canWrite); }
147                 }
148
149                 public virtual int Capacity {
150                         get {
151                                 CheckIfClosedThrowDisposed ();
152                                 return capacity - initialIndex;
153                         }
154
155                         set {
156                                 CheckIfClosedThrowDisposed ();
157                                 if (value == capacity)
158                                         return; // LAMENESS: see MemoryStreamTest.ConstructorFive
159
160                                 if (!expandable)
161                                         throw new NotSupportedException ("Cannot expand this MemoryStream");
162
163                                 if (value < 0 || value < length)
164                                         throw new ArgumentOutOfRangeException ("value",
165                                         "New capacity cannot be negative or less than the current capacity " + value + " " + capacity);
166
167                                 byte [] newBuffer = null;
168                                 if (value != 0) {
169                                         newBuffer = new byte [value];
170                                         Buffer.BlockCopy (internalBuffer, 0, newBuffer, 0, length);
171                                 }
172
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 #if NET_2_0
213                 protected override void Dispose (bool disposing)
214 #else
215                 public override void Close ()
216 #endif
217                 {
218                         streamClosed = true;
219                         expandable = false;
220                 }
221
222                 public override void Flush ()
223                 {
224                         // Do nothing
225                 }
226
227                 public virtual byte [] GetBuffer ()
228                 {
229                         if (!allowGetBuffer)
230                                 throw new UnauthorizedAccessException ();
231
232                         return internalBuffer;
233                 }
234
235                 public override int Read ([In,Out] byte [] buffer, int offset, int count)
236                 {
237                         CheckIfClosedThrowDisposed ();
238
239                         if (buffer == null)
240                                 throw new ArgumentNullException ("buffer");
241
242                         if (offset < 0 || count < 0)
243                                 throw new ArgumentOutOfRangeException ("offset or count less than zero.");
244
245                         if (buffer.Length - offset < count )
246                                 throw new ArgumentException ("offset+count",
247                                                               "The size of the buffer is less than offset + count.");
248
249                         if (position >= length || count == 0)
250                                 return 0;
251
252                         if (position > length - count)
253                                 count = length - position;
254
255                         Buffer.BlockCopy (internalBuffer, position, buffer, offset, count);
256                         position += count;
257                         return count;
258                 }
259
260                 public override int ReadByte ()
261                 {
262                         CheckIfClosedThrowDisposed ();
263                         if (position >= length)
264                                 return -1;
265
266                         return internalBuffer [position++];
267                 }
268
269                 public override long Seek (long offset, SeekOrigin loc)
270                 {
271                         CheckIfClosedThrowDisposed ();
272
273                         // It's funny that they don't throw this exception for < Int32.MinValue
274                         if (offset > (long) Int32.MaxValue)
275                                 throw new ArgumentOutOfRangeException ("Offset out of range. " + offset);
276
277                         int refPoint;
278                         switch (loc) {
279                         case SeekOrigin.Begin:
280                                 if (offset < 0)
281                                         throw new IOException ("Attempted to seek before start of MemoryStream.");
282                                 refPoint = initialIndex;
283                                 break;
284                         case SeekOrigin.Current:
285                                 refPoint = position;
286                                 break;
287                         case SeekOrigin.End:
288                                 refPoint = length;
289                                 break;
290                         default:
291                                 throw new ArgumentException ("loc", "Invalid SeekOrigin");
292                         }
293
294                         // LAMESPEC: My goodness, how may LAMESPECs are there in this
295                         // class! :)  In the spec for the Position property it's stated
296                         // "The position must not be more than one byte beyond the end of the stream."
297                         // In the spec for seek it says "Seeking to any location beyond the length of the 
298                         // stream is supported."  That's a contradiction i'd say.
299                         // I guess seek can go anywhere but if you use position it may get moved back.
300
301                         refPoint += (int) offset;
302                         if (refPoint < initialIndex)
303                                 throw new IOException ("Attempted to seek before start of MemoryStream.");
304
305                         position = refPoint;
306                         return position;
307                 }
308
309                 int CalculateNewCapacity (int minimum)
310                 {
311                         if (minimum < 256)
312                                 minimum = 256; // See GetBufferTwo test
313
314                         if (minimum < capacity * 2)
315                                 minimum = capacity * 2;
316
317                         return minimum;
318                 }
319
320                 public override void SetLength (long value)
321                 {
322                         if (!expandable && value > capacity)
323                                 throw new NotSupportedException ("Expanding this MemoryStream is not supported");
324
325                         CheckIfClosedThrowDisposed ();
326
327                         if (!canWrite) {
328                                 throw new NotSupportedException (Locale.GetText 
329                                         ("Cannot write to this MemoryStream"));
330                         }
331
332                         // LAMESPEC: AGAIN! It says to throw this exception if value is
333                         // greater than "the maximum length of the MemoryStream".  I haven't
334                         // seen anywhere mention what the maximum length of a MemoryStream is and
335                         // since we're this far this memory stream is expandable.
336                         if (value < 0 || (value + initialIndex) > (long) Int32.MaxValue)
337                                 throw new ArgumentOutOfRangeException ();
338
339                         int newSize = (int) value + initialIndex;
340                         if (newSize > capacity)
341                                 Capacity = CalculateNewCapacity (newSize);
342                         else if (newSize < length)
343                                 // zeroize present data (so we don't get it 
344                                 // back if we expand the stream using Seek)
345                                 Array.Clear (internalBuffer, newSize, length - newSize);
346
347                         length = newSize;
348                         if (position > length)
349                                 position = length;
350                 }
351
352                 public virtual byte [] ToArray ()
353                 {
354                         int l = length - initialIndex;
355                         byte[] outBuffer = new byte [l];
356
357                         if (internalBuffer != null)
358                                 Buffer.BlockCopy (internalBuffer, initialIndex, outBuffer, 0, l);
359                         return outBuffer; 
360                 }
361
362                 public override void Write (byte [] buffer, int offset, int count)
363                 {
364                         CheckIfClosedThrowDisposed ();
365
366                         if (!canWrite)
367                                 throw new NotSupportedException ("Cannot write to this stream.");
368
369                         if (buffer == null)
370                                 throw new ArgumentNullException ("buffer");
371                         
372                         if (offset < 0 || count < 0)
373                                 throw new ArgumentOutOfRangeException ();
374
375                         if (buffer.Length - offset < count)
376                                 throw new ArgumentException ("offset+count",
377                                                              "The size of the buffer is less than offset + count.");
378
379                         // reordered to avoid possible integer overflow
380                         if (position > capacity - count)
381                                 Capacity = CalculateNewCapacity (position + count);
382
383                         Buffer.BlockCopy (buffer, offset, internalBuffer, position, count);
384                         position += count;
385                         if (position >= length)
386                                 length = position;
387                 }
388
389                 public override void WriteByte (byte value)
390                 {
391                         CheckIfClosedThrowDisposed ();
392                         if (!canWrite)
393                                 throw new NotSupportedException ("Cannot write to this stream.");
394
395                         if (position >= capacity)
396                                 Capacity = CalculateNewCapacity (position + 1);
397
398                         if (position >= length)
399                                 length = position + 1;
400
401                         internalBuffer [position++] = value;
402                 }
403
404                 public virtual void WriteTo (Stream stream)
405                 {
406                         CheckIfClosedThrowDisposed ();
407
408                         if (stream == null)
409                                 throw new ArgumentNullException ("stream");
410
411                         stream.Write (internalBuffer, initialIndex, length - initialIndex);
412                 }
413         }               
414 }