MemoryStream.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. //
  2. // System.IO.MemoryStream
  3. //
  4. // Author: Marcin Szczepanski ([email protected])
  5. // Patrik Torstensson
  6. //
  7. // TODO: Clarify some of the lamespec issues
  8. //
  9. namespace System.IO {
  10. [Serializable]
  11. public class MemoryStream : Stream {
  12. private bool canRead;
  13. private bool canSeek;
  14. private bool canWrite;
  15. private bool allowGetBuffer;
  16. private int capacity;
  17. private byte[] internalBuffer;
  18. private int initialLength;
  19. private bool expandable;
  20. private bool streamClosed = false;
  21. private long position = 0;
  22. public MemoryStream() {
  23. canRead = true;
  24. canSeek = true;
  25. canWrite = true;
  26. capacity = 0;
  27. internalBuffer = new byte[0];
  28. allowGetBuffer = true;
  29. expandable = true;
  30. }
  31. public MemoryStream( byte[] buffer ) {
  32. InternalConstructor( buffer, 0, buffer.Length, true, false );
  33. }
  34. public MemoryStream( int capacity ) {
  35. canRead = true;
  36. canSeek = true;
  37. canWrite = true;
  38. this.capacity = capacity;
  39. initialLength = 0;
  40. internalBuffer = new byte [capacity];
  41. expandable = true;
  42. allowGetBuffer = true;
  43. }
  44. public MemoryStream( byte[] buffer, bool writeable ) {
  45. if( buffer == null ) {
  46. throw new ArgumentNullException();
  47. }
  48. InternalConstructor( buffer, 0, buffer.Length, writeable, true );
  49. }
  50. public MemoryStream( byte[] buffer, int index, int count ) {
  51. if( buffer == null ) {
  52. throw new ArgumentNullException();
  53. }
  54. InternalConstructor( buffer, index, count, true, false );
  55. }
  56. public MemoryStream( byte[] buffer, int index, int count, bool writeable ) {
  57. if( buffer == null ) {
  58. throw new ArgumentNullException();
  59. }
  60. InternalConstructor( buffer, index, count, writeable, true );
  61. }
  62. public MemoryStream( byte[] buffer, int index, int count, bool writeable, bool publicallyVisible ) {
  63. InternalConstructor( buffer, index, count, writeable, publicallyVisible );
  64. }
  65. private void InternalConstructor( byte[] buffer, int index, int count, bool writeable, bool publicallyVisible ) {
  66. if( buffer == null ) {
  67. throw new ArgumentNullException();
  68. } else if ( index < 0 || count < 0 ) {
  69. throw new ArgumentOutOfRangeException();
  70. } else if ( buffer.Length - index < count ) {
  71. throw new ArgumentException();
  72. }
  73. // LAMESPEC: The spec says to throw an UnauthorisedAccessException if
  74. // publicallyVisibile is fale?! Doesn't that defy the point of having
  75. // it there in the first place. I'll leave it out for now.
  76. canRead = true;
  77. canSeek = true;
  78. canWrite = writeable;
  79. initialLength = count;
  80. internalBuffer = new byte[ count ];
  81. capacity = count;
  82. Buffer.BlockCopyInternal (buffer, index, internalBuffer, 0, count);
  83. allowGetBuffer = publicallyVisible;
  84. expandable = false;
  85. }
  86. public override bool CanRead {
  87. get {
  88. return this.canRead;
  89. }
  90. }
  91. public override bool CanSeek {
  92. get {
  93. return this.canSeek;
  94. }
  95. }
  96. public override bool CanWrite {
  97. get {
  98. return this.canWrite;
  99. }
  100. }
  101. public virtual int Capacity {
  102. get {
  103. return this.capacity;
  104. }
  105. set {
  106. if( value < 0 || value < capacity ) {
  107. throw new ArgumentOutOfRangeException("value",
  108. "New capacity cannot be negative or less than the current capacity" );
  109. } else if( !expandable ) {
  110. throw new NotSupportedException( "Cannot expand this MemoryStream" );
  111. }
  112. byte[] newBuffer = new byte[ value ];
  113. Buffer.BlockCopyInternal (internalBuffer, 0, newBuffer, 0, capacity);
  114. capacity = value;
  115. }
  116. }
  117. public override long Length {
  118. get {
  119. // LAMESPEC: The spec says to throw an IOException if the
  120. // stream is closed and an ObjectDisposedException if
  121. // "methods were called after the stream was closed". What
  122. // is the difference?
  123. if( streamClosed ) {
  124. throw new IOException( "MemoryStream is closed" );
  125. }
  126. return internalBuffer.Length;
  127. }
  128. }
  129. public override long Position {
  130. get {
  131. if( streamClosed ) {
  132. throw new IOException( "MemoryStream is closed" );
  133. }
  134. return position;
  135. }
  136. set {
  137. if( position < 0 ) {
  138. throw new ArgumentOutOfRangeException ("value", "Position cannot be negative" );
  139. } else if (position > Int32.MaxValue) {
  140. throw new ArgumentOutOfRangeException ("value",
  141. "Length must be non-negative and less than 2^31 - 1 - origin");
  142. } else if( streamClosed ) {
  143. throw new IOException( "MemoryStream is closed" );
  144. }
  145. position = value;
  146. }
  147. }
  148. public override void Close() {
  149. if( streamClosed ) {
  150. return;
  151. }
  152. streamClosed = true;
  153. internalBuffer = null;
  154. }
  155. public override void Flush() { }
  156. public virtual byte[] GetBuffer() {
  157. if( !allowGetBuffer ) {
  158. throw new UnauthorizedAccessException();
  159. }
  160. return internalBuffer;
  161. }
  162. public override int Read( byte[] buffer, int offset, int count ) {
  163. if( buffer == null ) {
  164. throw new ArgumentNullException();
  165. } else if( offset < 0 || count < 0 ) {
  166. throw new ArgumentOutOfRangeException();
  167. } else if( buffer.Length - offset < count ) {
  168. throw new ArgumentException();
  169. } else if ( streamClosed ) {
  170. throw new ObjectDisposedException( "MemoryStream" );
  171. }
  172. long ReadTo;
  173. if( position + count > internalBuffer.Length ) {
  174. ReadTo = internalBuffer.Length;
  175. } else {
  176. ReadTo = position + (long)count;
  177. }
  178. Buffer.BlockCopyInternal (internalBuffer, (int)position, buffer, offset, (int)(ReadTo - position) );
  179. int bytesRead = (int)(ReadTo - position);
  180. position = ReadTo;
  181. return bytesRead;
  182. }
  183. public override int ReadByte( ) {
  184. if( streamClosed ) {
  185. throw new ObjectDisposedException( "MemoryStream" );
  186. }
  187. // LAMESPEC: What happens if we're at the end of the stream? It's unspecified in the
  188. // docs but tests against the MS impl. show it returns -1
  189. //
  190. if( position >= internalBuffer.Length ) {
  191. return -1;
  192. } else {
  193. return internalBuffer[ position++ ];
  194. }
  195. }
  196. public override long Seek( long offset, SeekOrigin loc ) {
  197. long refPoint;
  198. if( streamClosed ) {
  199. throw new ObjectDisposedException( "MemoryStream" );
  200. }
  201. switch( loc ) {
  202. case SeekOrigin.Begin:
  203. refPoint = 0;
  204. break;
  205. case SeekOrigin.Current:
  206. refPoint = position;
  207. break;
  208. case SeekOrigin.End:
  209. refPoint = internalBuffer.Length;
  210. break;
  211. default:
  212. throw new ArgumentException( "Invalid SeekOrigin" );
  213. }
  214. // LAMESPEC: My goodness, how may LAMESPECs are there in this
  215. // class! :) In the spec for the Position property it's stated
  216. // "The position must not be more than one byte beyond the end of the stream."
  217. // In the spec for seek it says "Seeking to any location beyond the length of the
  218. // stream is supported." That's a contradiction i'd say.
  219. // I guess seek can go anywhere but if you use position it may get moved back.
  220. if( refPoint + offset < 0 ) {
  221. throw new IOException( "Attempted to seek before start of MemoryStream" );
  222. } else if( offset > internalBuffer.Length ) {
  223. throw new ArgumentOutOfRangeException("offset",
  224. "Offset cannot be greater than length of MemoryStream" );
  225. }
  226. position = refPoint + offset;
  227. return position;
  228. }
  229. public override void SetLength( long value ) {
  230. if( streamClosed ) {
  231. throw new ObjectDisposedException( "MemoryStream" );
  232. } else if( !expandable && value > capacity ) {
  233. throw new NotSupportedException( "Expanding this MemoryStream is not supported" );
  234. } else if( !canWrite ) {
  235. throw new IOException( "Cannot write to this MemoryStream" );
  236. } else if( value < 0 ) {
  237. // LAMESPEC: AGAIN! It says to throw this exception if value is
  238. // greater than "the maximum length of the MemoryStream". I haven't
  239. // seen anywhere mention what the maximum length of a MemoryStream is and
  240. // since we're this far this memory stream is expandable.
  241. throw new ArgumentOutOfRangeException();
  242. }
  243. byte[] newBuffer;
  244. newBuffer = new byte[ value ];
  245. if (value < internalBuffer.Length) {
  246. // truncate
  247. Buffer.BlockCopyInternal (internalBuffer, 0, newBuffer, 0, (int)value );
  248. } else {
  249. // expand
  250. Buffer.BlockCopyInternal (internalBuffer, 0, newBuffer, 0, internalBuffer.Length );
  251. }
  252. internalBuffer = newBuffer;
  253. capacity = (int)value;
  254. }
  255. public virtual byte[] ToArray() {
  256. byte[] outBuffer = new byte[capacity];
  257. Buffer.BlockCopyInternal (internalBuffer, 0, outBuffer, 0, capacity);
  258. return outBuffer;
  259. }
  260. public override void Write( byte[] buffer, int offset, int count ) {
  261. if( buffer == null ) {
  262. throw new ArgumentNullException();
  263. } else if( !canWrite ) {
  264. throw new NotSupportedException();
  265. } else if( buffer.Length - offset < count ) {
  266. throw new ArgumentException();
  267. } else if( offset < 0 || count < 0 ) {
  268. throw new ArgumentOutOfRangeException();
  269. } else if( streamClosed ) {
  270. throw new ObjectDisposedException( "MemoryStream" );
  271. }
  272. if( position + count > capacity ) {
  273. if( expandable ) {
  274. // expand the buffer
  275. SetLength( position + count );
  276. } else {
  277. // only write as many bytes as will fit
  278. count = (int)((long)capacity - position);
  279. }
  280. }
  281. // internal buffer may not be allocated all the way up to capacity
  282. // count will already be limited to capacity above if non-expandable
  283. if( position + count >= internalBuffer.Length )
  284. SetLength( position + count );
  285. Buffer.BlockCopyInternal (buffer, offset, internalBuffer, (int)position, count);
  286. position += count;
  287. }
  288. public override void WriteByte( byte value ) {
  289. if ( streamClosed )
  290. throw new ObjectDisposedException( "MemoryStream" );
  291. else if( !canWrite || (position >= capacity && !expandable))
  292. throw new NotSupportedException();
  293. if( position >= internalBuffer.Length )
  294. SetLength ( position + 1 );
  295. internalBuffer[ position++ ] = value;
  296. }
  297. public virtual void WriteTo( Stream stream ) {
  298. if( stream == null ) {
  299. throw new ArgumentNullException();
  300. }
  301. stream.Write( internalBuffer, 0, internalBuffer.Length );
  302. }
  303. }
  304. }