BufferPoolMemoryStream.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. //
  2. // System.IO.MemoryStream.cs
  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. // Copyright (C) 2004 Novell (http://www.novell.com)
  11. //
  12. //
  13. // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  14. //
  15. // Permission is hereby granted, free of charge, to any person obtaining
  16. // a copy of this software and associated documentation files (the
  17. // "Software"), to deal in the Software without restriction, including
  18. // without limitation the rights to use, copy, modify, merge, publish,
  19. // distribute, sublicense, and/or sell copies of the Software, and to
  20. // permit persons to whom the Software is furnished to do so, subject to
  21. // the following conditions:
  22. //
  23. // The above copyright notice and this permission notice shall be
  24. // included in all copies or substantial portions of the Software.
  25. //
  26. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  27. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  28. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  29. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  30. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  31. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  32. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  33. //
  34. using System;
  35. using System.IO;
  36. using BestHTTP.PlatformSupport.Memory;
  37. namespace BestHTTP.Extensions
  38. {
  39. /// <summary>
  40. /// This is a modified MemoryStream class to use VariableSizedBufferPool
  41. /// </summary>
  42. public sealed class BufferPoolMemoryStream : System.IO.Stream
  43. {
  44. bool canWrite;
  45. bool allowGetBuffer;
  46. int capacity;
  47. int length;
  48. byte[] internalBuffer;
  49. int initialIndex;
  50. bool expandable;
  51. bool streamClosed;
  52. int position;
  53. int dirty_bytes;
  54. bool releaseInternalBuffer;
  55. public BufferPoolMemoryStream() : this(0)
  56. {
  57. }
  58. public BufferPoolMemoryStream(int capacity)
  59. {
  60. if (capacity < 0)
  61. throw new ArgumentOutOfRangeException("capacity");
  62. canWrite = true;
  63. //internalBuffer = capacity > 0 ? BufferPool.Get(capacity, true) : BufferPool.NoData;
  64. //this.capacity = internalBuffer.Length;
  65. //
  66. //expandable = true;
  67. //allowGetBuffer = true;
  68. var buffer = capacity > 0 ? BufferPool.Get(capacity, true) : BufferPool.NoData;
  69. InternalConstructor(buffer, 0, buffer.Length, true, true, true, true);
  70. }
  71. public BufferPoolMemoryStream(byte[] buffer)
  72. {
  73. if (buffer == null)
  74. throw new ArgumentNullException("buffer");
  75. InternalConstructor(buffer, 0, buffer.Length, true, false, true, false);
  76. }
  77. public BufferPoolMemoryStream(byte[] buffer, bool writable)
  78. {
  79. if (buffer == null)
  80. throw new ArgumentNullException("buffer");
  81. InternalConstructor(buffer, 0, buffer.Length, writable, false, true, false);
  82. }
  83. public BufferPoolMemoryStream(byte[] buffer, int index, int count)
  84. {
  85. InternalConstructor(buffer, index, count, true, false, true, false);
  86. }
  87. public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable)
  88. {
  89. InternalConstructor(buffer, index, count, writable, false, true, false);
  90. }
  91. public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
  92. {
  93. InternalConstructor(buffer, index, count, writable, publiclyVisible, true, false);
  94. }
  95. public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, bool releaseBuffer)
  96. {
  97. InternalConstructor(buffer, index, count, writable, publiclyVisible, releaseBuffer, false);
  98. }
  99. public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, bool releaseBuffer, bool canExpand)
  100. {
  101. InternalConstructor(buffer, index, count, writable, publiclyVisible, releaseBuffer, canExpand);
  102. }
  103. void InternalConstructor(byte[] buffer, int index, int count, bool writable, bool publicallyVisible, bool releaseBuffer, bool canExpand)
  104. {
  105. if (buffer == null)
  106. throw new ArgumentNullException("buffer");
  107. if (index < 0 || count < 0)
  108. throw new ArgumentOutOfRangeException("index or count is less than 0.");
  109. if (buffer.Length - index < count)
  110. throw new ArgumentException("index+count",
  111. "The size of the buffer is less than index + count.");
  112. canWrite = writable;
  113. internalBuffer = buffer;
  114. capacity = count + index;
  115. //length = capacity;
  116. length = 0;
  117. position = index;
  118. initialIndex = index;
  119. allowGetBuffer = publicallyVisible;
  120. releaseInternalBuffer = releaseBuffer;
  121. expandable = canExpand;
  122. }
  123. void CheckIfClosedThrowDisposed()
  124. {
  125. if (streamClosed)
  126. throw new ObjectDisposedException("MemoryStream");
  127. }
  128. public override bool CanRead
  129. {
  130. get { return !streamClosed; }
  131. }
  132. public override bool CanSeek
  133. {
  134. get { return !streamClosed; }
  135. }
  136. public override bool CanWrite
  137. {
  138. get { return (!streamClosed && canWrite); }
  139. }
  140. public int Capacity
  141. {
  142. get
  143. {
  144. CheckIfClosedThrowDisposed();
  145. return capacity - initialIndex;
  146. }
  147. set
  148. {
  149. CheckIfClosedThrowDisposed();
  150. if (value == capacity)
  151. return; // LAMENESS: see MemoryStreamTest.ConstructorFive
  152. if (!expandable)
  153. throw new NotSupportedException("Cannot expand this MemoryStream");
  154. if (value < 0 || value < length)
  155. throw new ArgumentOutOfRangeException("value",
  156. "New capacity cannot be negative or less than the current capacity " + value + " " + capacity);
  157. byte[] newBuffer = null;
  158. if (value != 0)
  159. {
  160. newBuffer = BufferPool.Get(value, true);
  161. Buffer.BlockCopy(internalBuffer, 0, newBuffer, 0, length);
  162. }
  163. dirty_bytes = 0; // discard any dirty area beyond previous length
  164. BufferPool.Release(internalBuffer);
  165. internalBuffer = newBuffer; // It's null when capacity is set to 0
  166. capacity = internalBuffer != null ? internalBuffer.Length : 0;
  167. }
  168. }
  169. public override long Length
  170. {
  171. get
  172. {
  173. // LAMESPEC: The spec says to throw an IOException if the
  174. // stream is closed and an ObjectDisposedException if
  175. // "methods were called after the stream was closed". What
  176. // is the difference?
  177. CheckIfClosedThrowDisposed();
  178. // This is ok for MemoryStreamTest.ConstructorFive
  179. return length - initialIndex;
  180. }
  181. }
  182. public override long Position
  183. {
  184. get
  185. {
  186. CheckIfClosedThrowDisposed();
  187. return position - initialIndex;
  188. }
  189. set
  190. {
  191. CheckIfClosedThrowDisposed();
  192. if (value < 0)
  193. throw new ArgumentOutOfRangeException("value",
  194. "Position cannot be negative");
  195. if (value > Int32.MaxValue)
  196. throw new ArgumentOutOfRangeException("value",
  197. "Position must be non-negative and less than 2^31 - 1 - origin");
  198. position = initialIndex + (int)value;
  199. }
  200. }
  201. protected override void Dispose (bool disposing)
  202. {
  203. streamClosed = true;
  204. expandable = false;
  205. if (disposing && internalBuffer != null && this.releaseInternalBuffer)
  206. BufferPool.Release(internalBuffer);
  207. internalBuffer = null;
  208. }
  209. public override void Flush()
  210. {
  211. // Do nothing
  212. }
  213. public byte[] GetBuffer()
  214. {
  215. if (!allowGetBuffer)
  216. throw new UnauthorizedAccessException();
  217. return internalBuffer;
  218. }
  219. public override int Read(byte[] buffer, int offset, int count)
  220. {
  221. CheckIfClosedThrowDisposed();
  222. if (buffer == null)
  223. throw new ArgumentNullException("buffer");
  224. if (offset < 0 || count < 0)
  225. throw new ArgumentOutOfRangeException("offset or count less than zero.");
  226. if (buffer.Length - offset < count)
  227. throw new ArgumentException("offset+count",
  228. "The size of the buffer is less than offset + count.");
  229. if (position >= length || count == 0)
  230. return 0;
  231. if (position > length - count)
  232. count = length - position;
  233. Buffer.BlockCopy(internalBuffer, position, buffer, offset, count);
  234. position += count;
  235. return count;
  236. }
  237. public override int ReadByte()
  238. {
  239. CheckIfClosedThrowDisposed();
  240. if (position >= length)
  241. return -1;
  242. return internalBuffer[position++];
  243. }
  244. public override long Seek(long offset, SeekOrigin loc)
  245. {
  246. CheckIfClosedThrowDisposed();
  247. // It's funny that they don't throw this exception for < Int32.MinValue
  248. if (offset > (long)Int32.MaxValue)
  249. throw new ArgumentOutOfRangeException("Offset out of range. " + offset);
  250. int refPoint;
  251. switch (loc)
  252. {
  253. case SeekOrigin.Begin:
  254. if (offset < 0)
  255. throw new IOException("Attempted to seek before start of MemoryStream.");
  256. refPoint = initialIndex;
  257. break;
  258. case SeekOrigin.Current:
  259. refPoint = position;
  260. break;
  261. case SeekOrigin.End:
  262. refPoint = length;
  263. break;
  264. default:
  265. throw new ArgumentException("loc", "Invalid SeekOrigin");
  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. refPoint += (int)offset;
  274. if (refPoint < initialIndex)
  275. throw new IOException("Attempted to seek before start of MemoryStream.");
  276. position = refPoint;
  277. return position;
  278. }
  279. int CalculateNewCapacity(int minimum)
  280. {
  281. if (minimum < 256)
  282. minimum = 256; // See GetBufferTwo test
  283. if (minimum < capacity * 2)
  284. minimum = capacity * 2;
  285. if (!UnityEngine.Mathf.IsPowerOfTwo(minimum))
  286. minimum = UnityEngine.Mathf.NextPowerOfTwo(minimum);
  287. return minimum;
  288. }
  289. void Expand(int newSize)
  290. {
  291. // We don't need to take into account the dirty bytes when incrementing the
  292. // Capacity, as changing it will only preserve the valid clear region.
  293. if (newSize > capacity)
  294. Capacity = CalculateNewCapacity(newSize);
  295. else if (dirty_bytes > 0)
  296. {
  297. Array.Clear(internalBuffer, length, dirty_bytes);
  298. dirty_bytes = 0;
  299. }
  300. }
  301. public override void SetLength(long value)
  302. {
  303. if (!expandable && value > capacity)
  304. throw new NotSupportedException("Expanding this MemoryStream is not supported");
  305. CheckIfClosedThrowDisposed();
  306. if (!canWrite)
  307. {
  308. throw new NotSupportedException("Cannot write to this MemoryStream");
  309. }
  310. // LAMESPEC: AGAIN! It says to throw this exception if value is
  311. // greater than "the maximum length of the MemoryStream". I haven't
  312. // seen anywhere mention what the maximum length of a MemoryStream is and
  313. // since we're this far this memory stream is expandable.
  314. if (value < 0 || (value + initialIndex) > (long)Int32.MaxValue)
  315. throw new ArgumentOutOfRangeException();
  316. int newSize = (int)value + initialIndex;
  317. if (newSize > length)
  318. Expand(newSize);
  319. else if (newSize < length) // Postpone the call to Array.Clear till expand time
  320. dirty_bytes += length - newSize;
  321. length = newSize;
  322. if (position > length)
  323. position = length;
  324. }
  325. public byte[] ToArray()
  326. {
  327. return ToArray(false);
  328. }
  329. public byte[] ToArray(bool canBeLarger)
  330. {
  331. int l = length - initialIndex;
  332. byte[] outBuffer = l > 0 ? BufferPool.Get(l, canBeLarger) : BufferPool.NoData;
  333. if (internalBuffer != null)
  334. Buffer.BlockCopy(internalBuffer, initialIndex, outBuffer, 0, l);
  335. return outBuffer;
  336. }
  337. public BufferSegment ToBufferSegment()
  338. {
  339. int l = length - initialIndex;
  340. byte[] outBuffer = l > 0 ? BufferPool.Get(l, true) : BufferPool.NoData;
  341. if (internalBuffer != null)
  342. Buffer.BlockCopy(internalBuffer, initialIndex, outBuffer, 0, l);
  343. return new BufferSegment(outBuffer, 0, l);
  344. }
  345. public override void Write(byte[] buffer, int offset, int count)
  346. {
  347. CheckIfClosedThrowDisposed();
  348. if (!canWrite)
  349. throw new NotSupportedException("Cannot write to this stream.");
  350. if (buffer == null)
  351. throw new ArgumentNullException("buffer");
  352. if (offset < 0 || count < 0)
  353. throw new ArgumentOutOfRangeException();
  354. if (buffer.Length - offset < count)
  355. throw new ArgumentException("offset+count",
  356. "The size of the buffer is less than offset + count.");
  357. // reordered to avoid possible integer overflow
  358. if (position > length - count)
  359. Expand(position + count);
  360. Buffer.BlockCopy(buffer, offset, internalBuffer, position, count);
  361. position += count;
  362. if (position >= length)
  363. length = position;
  364. }
  365. public override void WriteByte(byte value)
  366. {
  367. CheckIfClosedThrowDisposed();
  368. if (!canWrite)
  369. throw new NotSupportedException("Cannot write to this stream.");
  370. if (position >= length)
  371. {
  372. Expand(position + 1);
  373. length = position + 1;
  374. }
  375. internalBuffer[position++] = value;
  376. }
  377. public void WriteTo(Stream stream)
  378. {
  379. CheckIfClosedThrowDisposed();
  380. if (stream == null)
  381. throw new ArgumentNullException("stream");
  382. stream.Write(internalBuffer, initialIndex, length - initialIndex);
  383. }
  384. }
  385. }