MemoryStream.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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. public class MemoryStream : Stream {
  10. private bool canRead;
  11. private bool canSeek;
  12. private bool canWrite;
  13. private bool allowGetBuffer;
  14. private int capacity;
  15. private byte[] internalBuffer;
  16. private int initialLength;
  17. private bool expandable;
  18. private bool streamClosed = false;
  19. private long position = 0;
  20. public MemoryStream() {
  21. canRead = true;
  22. canSeek = true;
  23. canWrite = true;
  24. capacity = 0;
  25. internalBuffer = new byte[0];
  26. allowGetBuffer = true;
  27. expandable = true;
  28. }
  29. public MemoryStream( byte[] buffer ) {
  30. InternalConstructor( buffer, 0, buffer.Length, true, false );
  31. }
  32. public MemoryStream( int capacity ) {
  33. canRead = true;
  34. canSeek = true;
  35. canWrite = true;
  36. this.capacity = capacity;
  37. initialLength = capacity;
  38. internalBuffer = new byte[ capacity ];
  39. expandable = true;
  40. allowGetBuffer = true;
  41. }
  42. public MemoryStream( byte[] buffer, bool writeable ) {
  43. if( buffer == null ) {
  44. throw new ArgumentNullException();
  45. }
  46. InternalConstructor( buffer, 0, buffer.Length, writeable, true );
  47. }
  48. public MemoryStream( byte[] buffer, int index, int count ) {
  49. if( buffer == null ) {
  50. throw new ArgumentNullException();
  51. }
  52. InternalConstructor( buffer, index, count, true, false );
  53. }
  54. public MemoryStream( byte[] buffer, int index, int count, bool writeable ) {
  55. if( buffer == null ) {
  56. throw new ArgumentNullException();
  57. }
  58. InternalConstructor( buffer, index, count, writeable, true );
  59. }
  60. public MemoryStream( byte[] buffer, int index, int count, bool writeable, bool publicallyVisible ) {
  61. InternalConstructor( buffer, index, count, writeable, publicallyVisible );
  62. }
  63. private void InternalConstructor( byte[] buffer, int index, int count, bool writeable, bool publicallyVisible ) {
  64. if( buffer == null ) {
  65. throw new ArgumentNullException();
  66. } else if ( index < 0 || count < 0 ) {
  67. throw new ArgumentOutOfRangeException();
  68. } else if ( buffer.Length - index < count ) {
  69. throw new ArgumentException();
  70. }
  71. // LAMESPEC: The spec says to throw an UnauthorisedAccessException if
  72. // publicallyVisibile is fale?! Doesn't that defy the point of having
  73. // it there in the first place. I'll leave it out for now.
  74. canRead = true;
  75. canSeek = true;
  76. canWrite = writeable;
  77. initialLength = count;
  78. internalBuffer = new byte[ count ];
  79. capacity = count;
  80. Array.Copy( buffer, index, internalBuffer, 0, count );
  81. allowGetBuffer = publicallyVisible;
  82. expandable = false;
  83. }
  84. public override bool CanRead {
  85. get {
  86. return this.canRead;
  87. }
  88. }
  89. public override bool CanSeek {
  90. get {
  91. return this.canSeek;
  92. }
  93. }
  94. public override bool CanWrite {
  95. get {
  96. return this.canWrite;
  97. }
  98. }
  99. public virtual int Capacity {
  100. get {
  101. return this.capacity;
  102. }
  103. set {
  104. if( value < 0 || value < capacity ) {
  105. throw new ArgumentOutOfRangeException( "New capacity cannot be negative or less than the current capacity" );
  106. } else if( !expandable ) {
  107. throw new NotSupportedException( "Cannot expand this MemoryStream" );
  108. }
  109. byte[] newBuffer = new byte[ value ];
  110. Array.Copy( internalBuffer, 0, newBuffer, 0, capacity );
  111. capacity = value;
  112. }
  113. }
  114. public override long Length {
  115. get {
  116. // LAMESPEC: The spec says to throw an IOException if the
  117. // stream is closed and an ObjectDisposedException if
  118. // "methods were called after the stream was closed". What
  119. // is the difference?
  120. if( streamClosed ) {
  121. throw new IOException( "MemoryStream is closed" );
  122. }
  123. return internalBuffer.Length;
  124. }
  125. }
  126. public override long Position {
  127. get {
  128. if( streamClosed ) {
  129. throw new IOException( "MemoryStream is closed" );
  130. }
  131. return position;
  132. }
  133. set {
  134. if( position < 0 ) {
  135. throw new ArgumentOutOfRangeException( "Position cannot be negative" );
  136. } else if( streamClosed ) {
  137. throw new IOException( "MemoryStream is closed" );
  138. }
  139. position = value;
  140. if( position > internalBuffer.Length + 1 ) {
  141. position = internalBuffer.Length + 1;
  142. }
  143. }
  144. }
  145. public override void Close() {
  146. if( streamClosed ) {
  147. throw new IOException( "MemoryStream already closed" );
  148. }
  149. streamClosed = true;
  150. Dispose( true );
  151. }
  152. protected override void Dispose( bool disposing ) { }
  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( internalBuffer.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 < capacity ) {
  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. // LAMESPEC: !! It says that "offset" is "offset in buffer at which
  261. // to begin writing", I presume this should be "offset in buffer at which
  262. // to begin reading"
  263. public override void Write( byte[] buffer, int offset, int count ) {
  264. if( buffer == null ) {
  265. throw new ArgumentNullException();
  266. } else if( !canWrite ) {
  267. throw new NotSupportedException();
  268. } else if( buffer.Length - offset < count ) {
  269. throw new ArgumentException();
  270. } else if( offset < 0 || count < 0 ) {
  271. throw new ArgumentOutOfRangeException();
  272. } else if( streamClosed ) {
  273. throw new ObjectDisposedException( "MemoryStream" );
  274. }
  275. if( position + count > capacity ) {
  276. if( expandable ) {
  277. // expand the buffer
  278. SetLength( position + count );
  279. } else {
  280. // only write as many bytes as will fit
  281. count = (int)((long)capacity - position);
  282. }
  283. }
  284. Array.Copy( buffer, offset, internalBuffer, (int)position, count );
  285. position += count;
  286. }
  287. public override void WriteByte( byte value ) {
  288. if( streamClosed ) {
  289. throw new ObjectDisposedException( "MemoryStream" );
  290. }
  291. if( position >= capacity ) {
  292. SetLength( capacity + 1 );
  293. }
  294. internalBuffer[ position++ ] = value;
  295. }
  296. public virtual void WriteTo( Stream stream ) {
  297. if( stream == null ) {
  298. throw new ArgumentNullException();
  299. }
  300. stream.Write( internalBuffer, 0, internalBuffer.Length );
  301. }
  302. }
  303. }