2002-01-31 Duncan Mak <duncan@ximian.com>
[mono.git] / mcs / class / corlib / System.IO / MemoryStream.cs
1 //\r
2 // System.IO.MemoryStream \r
3 //\r
4 // Author: Marcin Szczepanski (marcins@zipworld.com.au)\r
5 //\r
6 // TODO: Clarify some of the lamespec issues\r
7 //\r
8 \r
9 namespace System.IO {\r
10         public class MemoryStream : Stream {\r
11                 private bool canRead;\r
12                 private bool canSeek;\r
13                 private bool canWrite;\r
14                 \r
15                 private bool allowGetBuffer;\r
16 \r
17                 private int capacity;\r
18 \r
19                 private byte[] internalBuffer;\r
20 \r
21                 private int initialLength;\r
22                 private bool expandable;\r
23 \r
24                 private bool streamClosed = false;\r
25 \r
26                 private long position = 0;\r
27                 \r
28                 public MemoryStream() {\r
29                         canRead = true;\r
30                         canSeek = true;\r
31                         canWrite = true;\r
32 \r
33                         capacity = 0;\r
34 \r
35                         internalBuffer = new byte[0];   \r
36 \r
37                         allowGetBuffer = true;\r
38                         expandable = true;\r
39                 }\r
40 \r
41                 public MemoryStream( byte[] buffer ) {\r
42                         InternalConstructor( buffer, 0, buffer.Length, true, false );                        \r
43                 }\r
44 \r
45                 public MemoryStream( int capacity ) {\r
46                         \r
47                         canRead = true;\r
48                         canSeek = true;\r
49                         canWrite = true;\r
50                         \r
51                         this.capacity = capacity;\r
52                         initialLength = capacity;\r
53                         internalBuffer = new byte[ capacity ];\r
54 \r
55                         expandable = true;\r
56                         allowGetBuffer = true;\r
57                 }\r
58 \r
59                 public MemoryStream( byte[] buffer, bool writeable ) {\r
60                         if( buffer == null ) {\r
61                                 throw new ArgumentNullException();\r
62                         }\r
63 \r
64                         InternalConstructor( buffer, 0, buffer.Length, writeable, true );\r
65 \r
66                 }\r
67  \r
68                 public MemoryStream( byte[] buffer, int index, int count ) { \r
69                         if( buffer == null ) {\r
70                                 throw new ArgumentNullException();\r
71                         }\r
72                         \r
73                         InternalConstructor( buffer, index, count, true, false );                                        \r
74                 }\r
75                 \r
76                 public MemoryStream( byte[] buffer, int index, int count, bool writeable ) { \r
77                         \r
78                         if( buffer == null ) {\r
79                                 throw new ArgumentNullException();\r
80                         }\r
81                         \r
82                         InternalConstructor( buffer, index, count, writeable, true );        \r
83                 }\r
84 \r
85                 public MemoryStream( byte[] buffer, int index, int count, bool writeable, bool publicallyVisible ) {\r
86                         InternalConstructor( buffer, index, count, writeable, publicallyVisible );\r
87                 }\r
88 \r
89                 private void InternalConstructor( byte[] buffer, int index, int count, bool writeable, bool publicallyVisible ) {\r
90                 \r
91                         if( buffer == null ) {\r
92                                 throw new ArgumentNullException();\r
93                         } else if ( index < 0 || count < 0 ) {\r
94                                 throw new ArgumentOutOfRangeException();\r
95                         } else if ( buffer.Length - index < count ) {\r
96                                 throw new ArgumentException();\r
97                         }\r
98 \r
99                         // LAMESPEC: The spec says to throw an UnauthorisedAccessException if\r
100                         // publicallyVisibile is fale?!  Doesn't that defy the point of having\r
101                         // it there in the first place.  I'll leave it out for now.\r
102                         \r
103                         canRead = true;\r
104                         canSeek = true;\r
105                         canWrite = writeable;\r
106 \r
107                         initialLength = count;\r
108 \r
109                         internalBuffer = new byte[ count ];\r
110                         capacity = count;\r
111 \r
112                         Array.Copy( buffer, index, internalBuffer, 0, count );\r
113 \r
114                         allowGetBuffer = publicallyVisible;\r
115                         expandable = false;                \r
116                  }\r
117 \r
118                  public override bool CanRead {\r
119                         get {\r
120                                 return this.canRead;\r
121                         }\r
122                 }\r
123 \r
124                 public override bool CanSeek {\r
125                         get {\r
126                                 return this.canSeek;\r
127                         }\r
128                 }\r
129 \r
130                 public override bool CanWrite {\r
131                         get {\r
132                                 return this.canWrite;\r
133                         }\r
134                 }\r
135 \r
136                 public virtual int Capacity {\r
137                         get {\r
138                                 return this.capacity;\r
139                         }\r
140 \r
141                         set {\r
142                                 if( value < 0 || value < capacity ) {\r
143                                         throw new ArgumentOutOfRangeException( "New capacity cannot be negative or less than the current capacity" );\r
144                                 } else if( !expandable ) {\r
145                                         throw new NotSupportedException( "Cannot expand this MemoryStream" );\r
146                                 }\r
147 \r
148                                 byte[] newBuffer = new byte[ value ];\r
149                                 Array.Copy( internalBuffer, 0, newBuffer, 0, capacity );\r
150                                 capacity = value;\r
151                         }\r
152                 }\r
153 \r
154                 public override long Length {\r
155                         get {\r
156                                 // LAMESPEC: The spec says to throw an IOException if the\r
157                                 // stream is closed and an ObjectDisposedException if\r
158                                 // "methods were called after the stream was closed".  What\r
159                                 // is the difference?\r
160 \r
161                                 if( streamClosed ) {\r
162                                         throw new IOException( "MemoryStream is closed" );\r
163                                 }\r
164                                 \r
165                                 return internalBuffer.Length;\r
166                         }\r
167                 }\r
168 \r
169                 public override long Position {\r
170                         get {\r
171                                 if( streamClosed ) {\r
172                                         throw new IOException( "MemoryStream is closed" );\r
173                                 }\r
174 \r
175                                 return position;\r
176                         }\r
177 \r
178                         set {\r
179 \r
180                                 if( position < 0 ) {\r
181                                         throw new ArgumentOutOfRangeException( "Position cannot be negative" );\r
182                                 } else if( streamClosed ) {\r
183                                         throw new IOException( "MemoryStream is closed" );\r
184                                 }\r
185                                 \r
186                                 position = value;\r
187 \r
188                                 if( position > internalBuffer.Length + 1 ) {\r
189                                         position = internalBuffer.Length + 1;\r
190                                 }\r
191                         }\r
192                 }\r
193                 \r
194                 public override void Close() {\r
195                         if( streamClosed ) {\r
196                                 throw new IOException( "MemoryStream already closed" );\r
197                         }\r
198 \r
199                         streamClosed = true;\r
200                         Dispose( true );\r
201                 }\r
202 \r
203                 protected override void Dispose( bool disposing ) { }\r
204 \r
205                 public override void Flush() { }\r
206 \r
207                 public virtual byte[] GetBuffer() {\r
208                         if( !allowGetBuffer ) {\r
209                                 throw new UnauthorizedAccessException();\r
210                         }  \r
211 \r
212                         return internalBuffer;\r
213                 }\r
214 \r
215                 public override int Read( byte[] buffer, int offset, int count ) {\r
216                         if( buffer == null ) {\r
217                                 throw new ArgumentNullException();\r
218                         } else if( offset < 0 || count < 0 ) {\r
219                                 throw new ArgumentOutOfRangeException();\r
220                         } else if( internalBuffer.Length - offset < count ) {\r
221                                 throw new ArgumentException();\r
222                         } else if ( streamClosed ) {\r
223                                 throw new ObjectDisposedException( "MemoryStream" );\r
224                         }\r
225 \r
226                         long ReadTo;\r
227 \r
228                         if( position + count > internalBuffer.Length ) {\r
229                                 ReadTo = internalBuffer.Length;\r
230                         } else {\r
231                                 ReadTo = position + (long)count;\r
232                         }\r
233 \r
234                         Array.Copy( internalBuffer, (int)position, buffer, offset, (int)(ReadTo - position) );\r
235 \r
236                         int bytesRead = (int)(ReadTo - position);\r
237 \r
238                         position = ReadTo;\r
239 \r
240                         return bytesRead;\r
241                 }\r
242 \r
243                 public override int ReadByte( ) {\r
244                         if( streamClosed ) {\r
245                                 throw new ObjectDisposedException( "MemoryStream" );\r
246                         }\r
247 \r
248 \r
249                         // LAMESPEC: What happens if we're at the end of the stream?  It's unspecified in the\r
250                         // docs but tests against the MS impl. show it returns -1\r
251                         //\r
252 \r
253                         if( position >= internalBuffer.Length ) {\r
254                                 return -1;\r
255                         } else {\r
256                                 return internalBuffer[ position++ ];\r
257                         }\r
258                 }\r
259                  \r
260                 public override long Seek( long offset, SeekOrigin loc ) { \r
261                         long refPoint;\r
262 \r
263                         if( streamClosed ) {\r
264                                 throw new ObjectDisposedException( "MemoryStream" );\r
265                         }\r
266 \r
267                         switch( loc ) {\r
268                                 case SeekOrigin.Begin:\r
269                                         refPoint = 0;\r
270                                         break;\r
271                                 case SeekOrigin.Current:\r
272                                         refPoint = position;\r
273                                         break;\r
274                                 case SeekOrigin.End:\r
275                                         refPoint = internalBuffer.Length;\r
276                                         break;\r
277                                 default:\r
278                                         throw new ArgumentException( "Invalid SeekOrigin" );\r
279                         }\r
280 \r
281                         // LAMESPEC: My goodness, how may LAMESPECs are there in this\r
282                         // class! :)  In the spec for the Position property it's stated\r
283                         // "The position must not be more than one byte beyond the end of the stream."\r
284                         // In the spec for seek it says "Seeking to any location beyond the length of the \r
285                         // stream is supported."  That's a contradiction i'd say.\r
286                         // I guess seek can go anywhere but if you use position it may get moved back.\r
287 \r
288                         if( refPoint + offset < 0 ) {\r
289                                 throw new IOException( "Attempted to seek before start of MemoryStream" );\r
290                         } else if( offset > internalBuffer.Length ) {\r
291                                 throw new ArgumentOutOfRangeException( "Offset cannot be greater than length of MemoryStream" );\r
292                         }\r
293 \r
294                         position = refPoint + offset;\r
295 \r
296                         return position;\r
297                 }\r
298                 \r
299                 \r
300                 public override void SetLength( long value ) { \r
301                         if( streamClosed ) {\r
302                                 throw new ObjectDisposedException( "MemoryStream" );                        \r
303                         } else if( !expandable && value > capacity ) {\r
304                                 throw new NotSupportedException( "Expanding this MemoryStream is not supported" );\r
305                         } else if( !canWrite ) {\r
306                                 throw new IOException( "Cannot write to this MemoryStream" );\r
307                         } else if( value < 0 ) {\r
308 \r
309                                 // LAMESPEC: AGAIN! It says to throw this exception if value is\r
310                                 // greater than "the maximum length of the MemoryStream".  I haven't\r
311                                 // seen anywhere mention what the maximum length of a MemoryStream is and\r
312                                 // since we're this far this memory stream is expandable.\r
313 \r
314                                 throw new ArgumentOutOfRangeException();\r
315                         } \r
316 \r
317                         byte[] newBuffer;\r
318                         newBuffer = new byte[ value ];\r
319                         \r
320                         if( value < capacity ) {\r
321                                 // truncate\r
322                                 Array.Copy( internalBuffer, 0, newBuffer, 0, (int)value );                              \r
323                         } else {\r
324                                 // expand\r
325                                  Array.Copy( internalBuffer, 0, newBuffer, 0, internalBuffer.Length );\r
326                         }\r
327                         internalBuffer = newBuffer;\r
328                         capacity = (int)value;\r
329 \r
330                 }\r
331                 \r
332                 \r
333                 public virtual byte[] ToArray() { \r
334                 \r
335                         if( streamClosed ) {\r
336                                 throw new ArgumentException( "The MemoryStream has been closed" );\r
337                         }\r
338                               \r
339                         byte[] outBuffer = new byte[capacity];\r
340                         Array.Copy( internalBuffer, 0, outBuffer, 0, capacity);\r
341                         return outBuffer; \r
342                 }\r
343 \r
344                 // LAMESPEC: !!  It says that "offset" is "offset in buffer at which\r
345                 // to begin writing", I presume this should be "offset in buffer at which\r
346                 // to begin reading"\r
347 \r
348                 public override void Write( byte[] buffer, int offset, int count ) { \r
349                         if( buffer == null ) {\r
350                                 throw new ArgumentNullException();\r
351                         } else if( !canWrite ) {\r
352                                 throw new NotSupportedException();\r
353                         } else if( buffer.Length - offset < count ) {\r
354                                 throw new ArgumentException();\r
355                         } else if( offset < 0 || count < 0 ) {\r
356                                 throw new ArgumentOutOfRangeException();\r
357                         } else if( streamClosed ) {\r
358                                 throw new ObjectDisposedException( "MemoryStream" );\r
359                         }\r
360 \r
361                         if( position + count > capacity ) {\r
362                                 if( expandable ) {\r
363                                         // expand the buffer\r
364                                         SetLength( position + count );                       \r
365                                 } else {\r
366                                         // only write as many bytes as will fit\r
367                                         count = (int)((long)capacity - position);\r
368                                 }\r
369                         }\r
370 \r
371                         Array.Copy( buffer, offset, internalBuffer, (int)position, count );\r
372                         position += count;\r
373                 \r
374                 }\r
375 \r
376 \r
377                 public override void WriteByte( byte value ) { \r
378                         if( streamClosed ) {\r
379                                 throw new ObjectDisposedException( "MemoryStream" );\r
380                         }\r
381 \r
382                         if( position >= capacity ) {\r
383                                 SetLength( capacity + 1 );\r
384                         }\r
385 \r
386                         internalBuffer[ position++ ] = value;\r
387                 }\r
388                 \r
389 \r
390                 public virtual void WriteTo( Stream stream ) { \r
391                         if( stream == null ) {\r
392                                 throw new ArgumentNullException();\r
393                         }\r
394 \r
395                         stream.Write( internalBuffer, 0, internalBuffer.Length );\r
396                 \r
397                 }\r
398                                \r
399                        \r
400         }               \r
401 \r
402 \r
403 }                      \r