MemoryStream.cs 17 KB

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