MultiChunkBuffer.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /* Copyright 2010-present MongoDB Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.IO;
  18. using System.Linq;
  19. namespace MongoDB.Bson.IO
  20. {
  21. /// <summary>
  22. /// An IByteBuffer that is backed by multiple chunks.
  23. /// </summary>
  24. public sealed class MultiChunkBuffer : IByteBuffer
  25. {
  26. // private fields
  27. private int _capacity;
  28. private int _chunkIndex;
  29. private List<IBsonChunk> _chunks;
  30. private readonly IBsonChunkSource _chunkSource;
  31. private bool _disposed;
  32. private bool _isReadOnly;
  33. private int _length;
  34. private List<int> _positions;
  35. // constructors
  36. /// <summary>
  37. /// Initializes a new instance of the <see cref="MultiChunkBuffer"/> class.
  38. /// </summary>
  39. /// <param name="chunkSource">The chunk pool.</param>
  40. /// <exception cref="System.ArgumentNullException">chunkPool</exception>
  41. public MultiChunkBuffer(IBsonChunkSource chunkSource)
  42. {
  43. if (chunkSource == null)
  44. {
  45. throw new ArgumentNullException("chunkSource");
  46. }
  47. _chunks = new List<IBsonChunk>();
  48. _chunkSource = chunkSource;
  49. _length = 0;
  50. _positions = new List<int> { 0 };
  51. }
  52. /// <summary>
  53. /// Initializes a new instance of the <see cref="MultiChunkBuffer"/> class.
  54. /// </summary>
  55. /// <param name="chunks">The chunks.</param>
  56. /// <param name="length">The length.</param>
  57. /// <param name="isReadOnly">Whether the buffer is read only.</param>
  58. /// <exception cref="System.ArgumentNullException">chunks</exception>
  59. public MultiChunkBuffer(IEnumerable<IBsonChunk> chunks, int? length = null, bool isReadOnly = false)
  60. {
  61. if (chunks == null)
  62. {
  63. throw new ArgumentNullException("chunks");
  64. }
  65. var materializedList = new List<IBsonChunk>(chunks);
  66. var capacity = 0;
  67. var positions = new List<int> { 0 };
  68. foreach (var chunk in materializedList)
  69. {
  70. capacity += chunk.Bytes.Count;
  71. positions.Add(capacity);
  72. }
  73. if (length.HasValue && (length.Value < 0 || length.Value > capacity))
  74. {
  75. throw new ArgumentOutOfRangeException("length");
  76. }
  77. _capacity = capacity;
  78. _chunks = materializedList;
  79. _isReadOnly = isReadOnly;
  80. _length = length ?? capacity;
  81. _positions = positions;
  82. }
  83. // public properties
  84. /// <inheritdoc/>
  85. public int Capacity
  86. {
  87. get
  88. {
  89. ThrowIfDisposed();
  90. return _isReadOnly ? _length : _capacity;
  91. }
  92. }
  93. /// <summary>
  94. /// Gets the chunk source.
  95. /// </summary>
  96. /// <value>
  97. /// The chunk source.
  98. /// </value>
  99. public IBsonChunkSource ChunkSource
  100. {
  101. get { return _chunkSource; }
  102. }
  103. /// <inheritdoc/>
  104. public bool IsReadOnly
  105. {
  106. get
  107. {
  108. ThrowIfDisposed();
  109. return _isReadOnly;
  110. }
  111. }
  112. /// <inheritdoc/>
  113. public int Length
  114. {
  115. get
  116. {
  117. ThrowIfDisposed();
  118. return _length;
  119. }
  120. set
  121. {
  122. ThrowIfDisposed();
  123. if (value < 0 || value > _capacity)
  124. {
  125. throw new ArgumentOutOfRangeException("value");
  126. }
  127. EnsureIsWritable();
  128. _length = value;
  129. }
  130. }
  131. // public methods
  132. /// <inheritdoc/>
  133. public ArraySegment<byte> AccessBackingBytes(int position)
  134. {
  135. ThrowIfDisposed();
  136. if (position < 0 || position > _length)
  137. {
  138. throw new ArgumentOutOfRangeException("position");
  139. }
  140. var chunkIndex = GetChunkIndex(position);
  141. if (chunkIndex < _chunks.Count)
  142. {
  143. var segment = _chunks[chunkIndex].Bytes;
  144. var chunkOffset = position - _positions[chunkIndex];
  145. var chunkRemaining = segment.Count - chunkOffset;
  146. return new ArraySegment<byte>(segment.Array, segment.Offset + chunkOffset, chunkRemaining);
  147. }
  148. else
  149. {
  150. if (_chunks.Count > 0)
  151. {
  152. var segment = _chunks[chunkIndex - 1].Bytes;
  153. return new ArraySegment<byte>(segment.Array, segment.Offset + segment.Count, 0);
  154. }
  155. else
  156. {
  157. return new ArraySegment<byte>(new byte[0], 0, 0);
  158. }
  159. }
  160. }
  161. /// <inheritdoc/>
  162. public void Clear(int position, int count)
  163. {
  164. ThrowIfDisposed();
  165. if (position < 0 || position > _length)
  166. {
  167. throw new ArgumentOutOfRangeException("position");
  168. }
  169. if (count < 0 || position + count > _length)
  170. {
  171. throw new ArgumentOutOfRangeException("count");
  172. }
  173. EnsureIsWritable();
  174. var chunkIndex = GetChunkIndex(position);
  175. var chunkOffset = position - _positions[chunkIndex];
  176. while (count > 0)
  177. {
  178. var segment = _chunks[chunkIndex].Bytes;
  179. var chunkRemaining = segment.Count - chunkOffset;
  180. var partialCount = Math.Min(count, chunkRemaining);
  181. Array.Clear(segment.Array, segment.Offset + chunkOffset, partialCount);
  182. chunkIndex += 1;
  183. chunkOffset = 0;
  184. count -= partialCount;
  185. }
  186. }
  187. /// <inheritdoc/>
  188. public void Dispose()
  189. {
  190. if (!_disposed)
  191. {
  192. _disposed = true;
  193. foreach (var chunk in _chunks)
  194. {
  195. chunk.Dispose();
  196. }
  197. _chunks = null;
  198. _positions = null;
  199. }
  200. }
  201. /// <inheritdoc/>
  202. public void EnsureCapacity(int minimumCapacity)
  203. {
  204. if (minimumCapacity < 0)
  205. {
  206. throw new ArgumentOutOfRangeException("minimumCapacity");
  207. }
  208. ThrowIfDisposed();
  209. EnsureIsWritable();
  210. if (_capacity < minimumCapacity)
  211. {
  212. ExpandCapacity(minimumCapacity);
  213. }
  214. }
  215. /// <inheritdoc/>
  216. public byte GetByte(int position)
  217. {
  218. ThrowIfDisposed();
  219. if (position < 0 || position >= _length)
  220. {
  221. throw new ArgumentOutOfRangeException("position");
  222. }
  223. var chunkIndex = GetChunkIndex(position);
  224. var chunkOffset = position - _positions[chunkIndex];
  225. var segment = _chunks[chunkIndex].Bytes;
  226. return segment.Array[segment.Offset + chunkOffset];
  227. }
  228. /// <inheritdoc/>
  229. public void GetBytes(int position, byte[] destination, int offset, int count)
  230. {
  231. ThrowIfDisposed();
  232. if (position < 0 || position > _length)
  233. {
  234. throw new ArgumentOutOfRangeException("position");
  235. }
  236. if (destination == null)
  237. {
  238. throw new ArgumentNullException("destination");
  239. }
  240. if (offset < 0 || offset > destination.Length)
  241. {
  242. throw new ArgumentOutOfRangeException("offset");
  243. }
  244. if (count < 0 || position + count > _length || offset + count > destination.Length)
  245. {
  246. throw new ArgumentOutOfRangeException("count");
  247. }
  248. var chunkIndex = GetChunkIndex(position);
  249. var chunkOffset = position - _positions[chunkIndex];
  250. while (count > 0)
  251. {
  252. var segment = _chunks[chunkIndex].Bytes;
  253. var chunkRemaining = segment.Count - chunkOffset;
  254. var partialCount = Math.Min(count, chunkRemaining);
  255. Buffer.BlockCopy(segment.Array, segment.Offset + chunkOffset, destination, offset, partialCount);
  256. chunkIndex += 1;
  257. chunkOffset = 0;
  258. count -= partialCount;
  259. offset += partialCount;
  260. }
  261. }
  262. /// <inheritdoc/>
  263. public IByteBuffer GetSlice(int position, int length)
  264. {
  265. ThrowIfDisposed();
  266. if (position < 0 || position > _length)
  267. {
  268. throw new ArgumentOutOfRangeException("position");
  269. }
  270. if (length < 0 || position + length > _length)
  271. {
  272. throw new ArgumentOutOfRangeException("length");
  273. }
  274. EnsureIsReadOnly();
  275. if (length == 0)
  276. {
  277. return new ByteArrayBuffer(new byte[0]);
  278. }
  279. var firstChunkIndex = GetChunkIndex(position);
  280. var lastChunkIndex = GetChunkIndex(position + length - 1);
  281. IByteBuffer forkedBuffer;
  282. if (firstChunkIndex == lastChunkIndex)
  283. {
  284. var forkedChunk = _chunks[firstChunkIndex].Fork();
  285. forkedBuffer = new SingleChunkBuffer(forkedChunk, forkedChunk.Bytes.Count, isReadOnly: true);
  286. }
  287. else
  288. {
  289. var forkedChunks = _chunks.Skip(firstChunkIndex).Take(lastChunkIndex - firstChunkIndex + 1).Select(c => c.Fork());
  290. var forkedBufferLength = _positions[lastChunkIndex + 1] - _positions[firstChunkIndex];
  291. forkedBuffer = new MultiChunkBuffer(forkedChunks, forkedBufferLength, isReadOnly: true);
  292. }
  293. var offset = position - _positions[firstChunkIndex];
  294. return new ByteBufferSlice(forkedBuffer, offset, length);
  295. }
  296. /// <inheritdoc/>
  297. public void MakeReadOnly()
  298. {
  299. ThrowIfDisposed();
  300. _isReadOnly = true;
  301. }
  302. /// <inheritdoc/>
  303. public void SetByte(int position, byte value)
  304. {
  305. ThrowIfDisposed();
  306. if (position < 0 || position >= _length)
  307. {
  308. throw new ArgumentOutOfRangeException("position");
  309. }
  310. EnsureIsWritable();
  311. var chunkIndex = GetChunkIndex(position);
  312. var chunkOffset = position - _positions[chunkIndex];
  313. var segment = _chunks[chunkIndex].Bytes;
  314. segment.Array[segment.Offset + chunkOffset] = value;
  315. }
  316. /// <inheritdoc/>
  317. public void SetBytes(int position, byte[] source, int offset, int count)
  318. {
  319. ThrowIfDisposed();
  320. if (position < 0 || position > _length)
  321. {
  322. throw new ArgumentOutOfRangeException("position");
  323. }
  324. if (source == null)
  325. {
  326. throw new ArgumentNullException("source");
  327. }
  328. if (offset < 0 || offset > source.Length)
  329. {
  330. throw new ArgumentOutOfRangeException("offset");
  331. }
  332. if (count < 0 || position + count > _length || offset + count > source.Length)
  333. {
  334. throw new ArgumentOutOfRangeException("count");
  335. }
  336. EnsureIsWritable();
  337. var chunkIndex = GetChunkIndex(position);
  338. var chunkOffset = position - _positions[chunkIndex];
  339. while (count > 0)
  340. {
  341. var segment = _chunks[chunkIndex].Bytes;
  342. var chunkRemaining = segment.Count - chunkOffset;
  343. var partialCount = Math.Min(count, chunkRemaining);
  344. Buffer.BlockCopy(source, offset, segment.Array, segment.Offset + chunkOffset, partialCount);
  345. chunkIndex += 1;
  346. chunkOffset = 0;
  347. offset += partialCount;
  348. count -= partialCount;
  349. }
  350. }
  351. // private methods
  352. private void EnsureIsReadOnly()
  353. {
  354. if (!_isReadOnly)
  355. {
  356. throw new InvalidOperationException("MultiChunkBuffer is not read only.");
  357. }
  358. }
  359. private void EnsureIsWritable()
  360. {
  361. if (_isReadOnly)
  362. {
  363. throw new InvalidOperationException("MultiChunkBuffer is not writable.");
  364. }
  365. }
  366. private void ExpandCapacity(int minimumCapacity)
  367. {
  368. if (_chunkSource == null)
  369. {
  370. throw new InvalidOperationException("Capacity cannot be expanded because this buffer was created without specifying a chunk source.");
  371. }
  372. while (_capacity < minimumCapacity)
  373. {
  374. var chunk = _chunkSource.GetChunk(minimumCapacity);
  375. _chunks.Add(chunk);
  376. var newCapacity = (long)_capacity + (long)chunk.Bytes.Count;
  377. if (newCapacity > int.MaxValue)
  378. {
  379. throw new InvalidOperationException("Capacity is limited to 2GB.");
  380. }
  381. _capacity = (int)newCapacity;
  382. _positions.Add(_capacity);
  383. }
  384. }
  385. private int GetChunkIndex(int position)
  386. {
  387. // locality of reference means this loop will only execute once most of the time
  388. while (true)
  389. {
  390. if (_chunkIndex + 1 < _positions.Count && position >= _positions[_chunkIndex + 1])
  391. {
  392. _chunkIndex++;
  393. }
  394. else if (position < _positions[_chunkIndex])
  395. {
  396. _chunkIndex--;
  397. }
  398. else
  399. {
  400. return _chunkIndex;
  401. }
  402. }
  403. }
  404. private void ThrowIfDisposed()
  405. {
  406. if (_disposed)
  407. {
  408. throw new ObjectDisposedException(GetType().Name);
  409. }
  410. }
  411. }
  412. }