2003-11-14 Ben Maurer <bmaurer@users.sourceforge.net>
[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 //
11
12 using System.Runtime.InteropServices;
13
14 namespace System.IO
15 {
16         [Serializable]
17         public class MemoryStream : Stream
18         {
19                 bool canWrite;
20                 bool allowGetBuffer;
21                 int capacity;
22                 int length;
23                 byte [] internalBuffer;
24                 int initialIndex;
25                 bool expandable;
26                 bool streamClosed;
27                 int position;
28
29                 public MemoryStream () : this (0)
30                 {
31                 }
32
33                 public MemoryStream (int capacity)
34                 {
35                         if (capacity < 0)
36                                 throw new ArgumentOutOfRangeException ("capacity");
37
38                         canWrite = true;
39
40                         this.capacity = capacity;
41                         internalBuffer = new byte [capacity];
42
43                         expandable = true;
44                         allowGetBuffer = true;
45                 }
46
47                 public MemoryStream (byte [] buffer)
48                 {
49                         if (buffer == null)
50                                 throw new ArgumentNullException ("buffer");
51                         
52                         InternalConstructor (buffer, 0, buffer.Length, true, false);                        
53                 }
54
55                 public MemoryStream (byte [] buffer, bool writeable)
56                 {
57                         if (buffer == null)
58                                 throw new ArgumentNullException ("buffer");
59                         
60                         InternalConstructor (buffer, 0, buffer.Length, writeable, false);
61                 }
62
63                 public MemoryStream (byte [] buffer, int index, int count)
64                 {
65                         InternalConstructor (buffer, index, count, true, false);
66                 }
67
68                 public MemoryStream (byte [] buffer, int index, int count, bool writeable)
69                 {
70                         InternalConstructor (buffer, index, count, writeable, false);
71                 }
72
73                 public MemoryStream (byte [] buffer, int index, int count, bool writeable, bool publicallyVisible)
74                 {
75                         InternalConstructor (buffer, index, count, writeable, publicallyVisible);
76                 }
77
78                 void InternalConstructor (byte [] buffer, int index, int count, bool writeable, bool publicallyVisible)
79                 {
80                         if (buffer == null)
81                                 throw new ArgumentNullException ("buffer");
82
83                         if (index < 0 || count < 0)
84                                 throw new ArgumentOutOfRangeException ("index or count is less than 0.");
85
86                         if (buffer.Length - index < count)
87                                 throw new ArgumentException ("index+count", 
88                                                              "The size of the buffer is less than index + count.");
89
90                         canWrite = writeable;
91
92                         internalBuffer = buffer;
93                         capacity = count + index;
94                         length = capacity;
95                         position = index;
96                         initialIndex = index;
97
98                         allowGetBuffer = publicallyVisible;
99                         expandable = false;                
100                 }
101
102                 void CheckIfClosedThrowDisposed ()
103                 {
104                         if (streamClosed)
105                                 throw new ObjectDisposedException ("MemoryStream");
106                 }
107                 
108                 void CheckIfClosedThrowIO ()
109                 {
110                         if (streamClosed)
111                                 throw new IOException ("MemoryStream is closed");
112                 }
113                 
114                 public override bool CanRead {
115                         get { return !streamClosed; }
116                 }
117
118                 public override bool CanSeek {
119                         get { return !streamClosed; }
120                 }
121
122                 public override bool CanWrite {
123                         get { return (!streamClosed && canWrite); }
124                 }
125
126                 public virtual int Capacity {
127                         get {
128                                 CheckIfClosedThrowDisposed ();
129                                 return capacity - initialIndex;
130                         }
131
132                         set {
133                                 CheckIfClosedThrowDisposed ();
134                                 if (value == capacity)
135                                         return; // LAMENESS: see MemoryStreamTest.ConstructorFive
136
137                                 if (!expandable)
138                                         throw new NotSupportedException ("Cannot expand this MemoryStream");
139
140                                 if (value < 0 || value < length)
141                                         throw new ArgumentOutOfRangeException ("value",
142                                         "New capacity cannot be negative or less than the current capacity " + value + " " + capacity);
143
144                                 byte [] newBuffer = null;
145                                 if (value != 0) {
146                                         newBuffer = new byte [value];
147                                         Buffer.BlockCopyInternal (internalBuffer, 0, newBuffer, 0, length);
148                                 }
149
150                                 internalBuffer = newBuffer; // It's null when capacity is set to 0
151                                 capacity = value;
152                         }
153                 }
154
155                 public override long Length {
156                         get {
157                                 // LAMESPEC: The spec says to throw an IOException if the
158                                 // stream is closed and an ObjectDisposedException if
159                                 // "methods were called after the stream was closed".  What
160                                 // is the difference?
161
162                                 CheckIfClosedThrowIO ();
163
164                                 // This is ok for MemoryStreamTest.ConstructorFive
165                                 return length - initialIndex;
166                         }
167                 }
168
169                 public override long Position {
170                         get {
171                                 CheckIfClosedThrowIO ();
172                                 return position - initialIndex;
173                         }
174
175                         set {
176                                 CheckIfClosedThrowIO ();
177                                 if (value < 0)
178                                         throw new ArgumentOutOfRangeException ("value",
179                                                                 "Position cannot be negative" );
180
181                                 if (value > Int32.MaxValue)
182                                         throw new ArgumentOutOfRangeException ("value",
183                                         "Position must be non-negative and less than 2^31 - 1 - origin");
184
185                                 position = initialIndex + (int) value;
186                         }
187                 }
188
189                 public override void Close ()
190                 {
191                         streamClosed = true;
192                         expandable = false;
193                 }
194
195                 public override void Flush ()
196                 {
197                         // Do nothing
198                 }
199
200                 public virtual byte [] GetBuffer ()
201                 {
202                         if (!allowGetBuffer)
203                                 throw new UnauthorizedAccessException ();
204
205                         return internalBuffer;
206                 }
207
208                 public override int Read ([In,Out] byte [] buffer, int offset, int count)
209                 {
210                         CheckIfClosedThrowDisposed ();
211
212                         if (buffer == null)
213                                 throw new ArgumentNullException ("buffer");
214
215                         if (offset < 0 || count < 0)
216                                 throw new ArgumentOutOfRangeException ("offset or count less than zero.");
217
218                         if (buffer.Length - offset < count )
219                                 throw new ArgumentException ("offset+count",
220                                                               "The size of the buffer is less than offset + count.");
221
222                         if (position >= length || count == 0)
223                                 return 0;
224
225                         if (position > length - count)
226                                 count = length - position;
227
228                         Buffer.BlockCopyInternal (internalBuffer, position, buffer, offset, count);
229                         position += count;
230                         return count;
231                 }
232
233                 public override int ReadByte ()
234                 {
235                         CheckIfClosedThrowDisposed ();
236                         if (position >= length)
237                                 return -1;
238
239                         return internalBuffer [position++];
240                 }
241
242                 public override long Seek (long offset, SeekOrigin loc)
243                 {
244                         CheckIfClosedThrowDisposed ();
245
246                         // It's funny that they don't throw this exception for < Int32.MinValue
247                         if (offset > (long) Int32.MaxValue)
248                                 throw new ArgumentOutOfRangeException ("Offset out of range. " + offset);
249
250                         int refPoint;
251                         switch (loc) {
252                         case SeekOrigin.Begin:
253                                 if (offset < 0)
254                                         throw new IOException ("Attempted to seek before start of MemoryStream.");
255                                 refPoint = initialIndex;
256                                 break;
257                         case SeekOrigin.Current:
258                                 refPoint = position;
259                                 break;
260                         case SeekOrigin.End:
261                                 refPoint = length;
262                                 break;
263                         default:
264                                 throw new ArgumentException ("loc", "Invalid SeekOrigin");
265                         }
266
267                         // LAMESPEC: My goodness, how may LAMESPECs are there in this
268                         // class! :)  In the spec for the Position property it's stated
269                         // "The position must not be more than one byte beyond the end of the stream."
270                         // In the spec for seek it says "Seeking to any location beyond the length of the 
271                         // stream is supported."  That's a contradiction i'd say.
272                         // I guess seek can go anywhere but if you use position it may get moved back.
273
274                         refPoint += (int) offset;
275                         if (refPoint < initialIndex)
276                                 throw new IOException ("Attempted to seek before start of MemoryStream.");
277
278                         position = refPoint;
279                         return position;
280                 }
281
282                 int CalculateNewCapacity (int minimum)
283                 {
284                         if (minimum < 256)
285                                 minimum = 256; // See GetBufferTwo test
286
287                         if (minimum < capacity * 2)
288                                 minimum = capacity * 2;
289
290                         return minimum;
291                 }
292
293                 public override void SetLength (long value)
294                 {
295                         if (!expandable && value > capacity)
296                                 throw new NotSupportedException ("Expanding this MemoryStream is not supported");
297
298                         CheckIfClosedThrowDisposed ();
299
300                         if (!canWrite)
301                                 throw new IOException ("Cannot write to this MemoryStream");
302
303                         // LAMESPEC: AGAIN! It says to throw this exception if value is
304                         // greater than "the maximum length of the MemoryStream".  I haven't
305                         // seen anywhere mention what the maximum length of a MemoryStream is and
306                         // since we're this far this memory stream is expandable.
307                         if (value < 0 || (value + initialIndex) > (long) Int32.MaxValue)
308                                 throw new ArgumentOutOfRangeException ();
309
310                         int newSize = (int) value + initialIndex;
311                         if (newSize > capacity) {
312                                 Capacity = CalculateNewCapacity (newSize);
313                         } else if (newSize > length) {
314                                 for (int i = newSize; i < length; i++)
315                                         Buffer.SetByte (internalBuffer, i, 0);
316                         }
317
318                         length = newSize;
319                         if (position > length)
320                                 position = length;
321                 }
322
323                 public virtual byte [] ToArray ()
324                 {
325                         int l = length - initialIndex;
326                         byte[] outBuffer = new byte [l];
327
328                         Buffer.BlockCopyInternal (internalBuffer, initialIndex, outBuffer, 0, l);
329                         return outBuffer; 
330                 }
331
332                 public override void Write (byte [] buffer, int offset, int count)
333                 {
334                         CheckIfClosedThrowDisposed ();
335
336                         if (!canWrite)
337                                 throw new NotSupportedException ("Cannot write to this stream.");
338
339                         if (buffer == null)
340                                 throw new ArgumentNullException ("buffer");
341                         
342                         if (offset < 0 || count < 0)
343                                 throw new ArgumentOutOfRangeException ();
344
345                         if (buffer.Length - offset < count)
346                                 throw new ArgumentException ("offset+count",
347                                                              "The size of the buffer is less than offset + count.");
348
349                         if (position + count > capacity)
350                                 Capacity = CalculateNewCapacity (position + count);
351
352                         Buffer.BlockCopyInternal (buffer, offset, internalBuffer, position, count);
353                         position += count;
354                         if (position >= length)
355                                 length = position;
356                 }
357
358                 public override void WriteByte (byte value)
359                 {
360                         CheckIfClosedThrowDisposed ();
361                         if (!canWrite)
362                                 throw new NotSupportedException ("Cannot write to this stream.");
363
364                         if (position >= capacity)
365                                 Capacity = CalculateNewCapacity (position + 1);
366
367                         if (position >= length)
368                                 length = position + 1;
369
370                         internalBuffer [position++] = value;
371                 }
372
373                 public virtual void WriteTo (Stream stream)
374                 {
375                         CheckIfClosedThrowDisposed ();
376
377                         if (stream == null)
378                                 throw new ArgumentNullException ("stream");
379
380                         stream.Write (internalBuffer, initialIndex, length - initialIndex);
381                 }
382         }               
383 }