PageRenderTime 34ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/4.0/Source/Extensions/Zlib/BinaryChunkQueue.cs

#
C# | 395 lines | 233 code | 59 blank | 103 comment | 59 complexity | a64bb037508b0fdd3d354fa4d96e8179 MD5 | raw file
Possible License(s): CPL-1.0, GPL-2.0, CC-BY-SA-3.0, MPL-2.0-no-copyleft-exception, Apache-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using PHP.Core;
  6. using System.Diagnostics;
  7. namespace PHP.Library.Zlib
  8. {
  9. /// <summary>
  10. /// Queue of binary chunks that can be pushed to and popped from.
  11. /// </summary>
  12. internal class BinaryChunkQueue
  13. {
  14. /// <summary>
  15. /// Internal representation of chunk.
  16. /// </summary>
  17. private class Chunk
  18. {
  19. /// <summary>
  20. /// Byte array.
  21. /// </summary>
  22. public byte[] Bytes { get; private set; }
  23. /// <summary>
  24. /// Starting offset.
  25. /// </summary>
  26. public int Offset { get; private set; }
  27. /// <summary>
  28. /// Length of the chunk.
  29. /// </summary>
  30. public int Length { get; private set; }
  31. /// <summary>
  32. /// Initializes new binary chunk.
  33. /// </summary>
  34. /// <param name="chunk">Non-null reference to byte array.</param>
  35. /// <param name="offset">Starting offset in the chunk.</param>
  36. /// <param name="length">Length of the valid area in the chunk.</param>
  37. public Chunk(byte[] chunk, int offset, int length)
  38. {
  39. Bytes = chunk;
  40. Offset = offset;
  41. Length = length;
  42. }
  43. }
  44. /// <summary>
  45. /// Available bytes within the binary chunk queue.
  46. /// </summary>
  47. private int _availableBytes;
  48. /// <summary>
  49. /// List of chunks.
  50. /// </summary>
  51. private LinkedList<Chunk> _chunks;
  52. /// <summary>
  53. /// Position within the first chunk, if possible (otherwise -1).
  54. /// </summary>
  55. private int _position;
  56. /// <summary>
  57. /// Gets total available bytes within the queue.
  58. /// </summary>
  59. public int AvailableBytes { get { return _availableBytes; } }
  60. /// <summary>
  61. /// Pushes new chunk into the queue.
  62. /// </summary>
  63. /// <param name="chunk">Byte array.</param>
  64. public void Push(byte[] chunk)
  65. {
  66. EnqueueByteBlock(chunk, 0, chunk.Length);
  67. }
  68. /// <summary>
  69. /// Pushes new chunk into the queue.
  70. /// </summary>
  71. /// <param name="chunk">Byte array.</param>
  72. /// <param name="offset">Offset of the valid area of the chunk.</param>
  73. /// <param name="length">Length of valid area of the chunk.</param>
  74. public void EnqueueByteBlock(byte[] chunk, int offset, int length)
  75. {
  76. Debug.Assert(chunk != null);
  77. Debug.Assert(offset >= 0 && offset < chunk.Length);
  78. Debug.Assert(length > 0 && offset + length <= chunk.Length);
  79. Chunk newChunk = new Chunk(chunk, offset, length);
  80. _chunks.AddLast(newChunk);
  81. _availableBytes += length;
  82. if (_position == -1)
  83. {
  84. Debug.Assert(_chunks.First.Value == newChunk);
  85. _position = newChunk.Offset;
  86. }
  87. }
  88. /// <summary>
  89. /// Pushes new chunk into the beginning of the queue.
  90. /// </summary>
  91. /// <param name="chunk">Byte array.</param>
  92. /// <param name="offset">Offset of the valid area of the chunk.</param>
  93. /// <param name="length">Length of valid area of the chunk.</param>
  94. public void PushByteBlock(byte[] chunk, int offset, int length)
  95. {
  96. Debug.Assert(chunk != null);
  97. Debug.Assert(offset >= 0 && offset < chunk.Length);
  98. Debug.Assert(length > 0 && offset + length <= chunk.Length);
  99. if (_chunks.First != null && _position != _chunks.First.Value.Offset)
  100. {
  101. //change the first chunk if needed
  102. Chunk old = _chunks.First.Value;
  103. Chunk replacement = new Chunk(old.Bytes, _position, old.Length - (_position - old.Offset));
  104. _chunks.RemoveFirst();
  105. _chunks.AddFirst(replacement);
  106. }
  107. Chunk newChunk = new Chunk(chunk, offset, length);
  108. _chunks.AddFirst(newChunk);
  109. _availableBytes += length;
  110. _position = newChunk.Offset;
  111. }
  112. /// <summary>
  113. /// Pops a single byte from the queue, removing it in the process.
  114. /// </summary>
  115. /// <returns>Next byte value if there was any present, otherwise null.</returns>
  116. public byte? DequeueByte()
  117. {
  118. if (_chunks.First == null)
  119. {
  120. Debug.Assert(_position == -1);
  121. return null;
  122. }
  123. else
  124. {
  125. Chunk current = _chunks.First.Value;
  126. Debug.Assert(_position >= current.Offset);
  127. Debug.Assert(_position < current.Offset + current.Length);
  128. Debug.Assert(_availableBytes > 0);
  129. byte ret = current.Bytes[_position];
  130. _position++;
  131. _availableBytes--;
  132. if (_position >= current.Offset + current.Length)
  133. {
  134. // move to the next chunk and remove the first
  135. _chunks.RemoveFirst();
  136. if (_chunks.First == null)
  137. {
  138. _position = -1;
  139. }
  140. else
  141. {
  142. _position = _chunks.First.Value.Offset;
  143. }
  144. }
  145. return ret;
  146. }
  147. }
  148. /// <summary>
  149. /// Peeks a single byte from the queue.
  150. /// </summary>
  151. /// <returns>Next byte value if there was any present, otherwise null.</returns>
  152. public byte? PeekByte()
  153. {
  154. if (_chunks.First == null)
  155. {
  156. Debug.Assert(_position == -1);
  157. return null;
  158. }
  159. else
  160. {
  161. Chunk current = _chunks.First.Value;
  162. Debug.Assert(_position >= current.Offset);
  163. Debug.Assert(_position < current.Offset + current.Length);
  164. return current.Bytes[_position];
  165. }
  166. }
  167. /// <summary>
  168. /// Pops a byte block from the queue, removing it in the process.
  169. /// </summary>
  170. /// <param name="length">Requested length of the block.</param>
  171. /// <returns>Block of bytes of requested length if available, otherwise null.</returns>
  172. public byte[] DequeueByteBlock(int length)
  173. {
  174. // non-negative length
  175. Debug.Assert(length >= 0);
  176. if (length == 0)
  177. {
  178. // fast branch for requested zero length
  179. return new byte[0];
  180. }
  181. else if (_availableBytes < length)
  182. {
  183. // block of that length is not available
  184. return null;
  185. }
  186. else if (_position == 0 && _chunks.First != null && _chunks.First.Value.Offset == 0 && _chunks.First.Value.Length == length)
  187. {
  188. // fast track for getting the whole first block without copying
  189. byte[] ret = _chunks.First.Value.Bytes;
  190. // remove the first block
  191. _chunks.RemoveFirst();
  192. //update available bytes
  193. _availableBytes -= length;
  194. //update position;
  195. if (_chunks.First == null)
  196. {
  197. _position = -1;
  198. }
  199. else
  200. {
  201. _position = _chunks.First.Value.Offset;
  202. }
  203. return ret;
  204. }
  205. else
  206. {
  207. byte[] block = new byte[length];
  208. int offset = 0;
  209. while (offset < length)
  210. {
  211. // there should always be chunk available
  212. Debug.Assert(_chunks.First != null);
  213. Chunk record = _chunks.First.Value;
  214. int remainingLength = record.Offset + record.Length - _position;
  215. int copyLength = length - offset < remainingLength ? length - offset : remainingLength;
  216. // position should be inside valid area of the chunk and not past the end
  217. Debug.Assert(_position >= record.Offset && _position < record.Offset + record.Length);
  218. // available bytes should be higher than chunk's remaining bytes
  219. Debug.Assert(_availableBytes >= remainingLength);
  220. // length to copy should be positive and not higher than block's remaining bytes
  221. Debug.Assert(copyLength > 0 && copyLength <= remainingLength);
  222. // perform the operation
  223. Buffer.BlockCopy(
  224. record.Bytes,
  225. _position,
  226. block,
  227. offset,
  228. copyLength);
  229. // update offset and available bytes
  230. offset += copyLength;
  231. _availableBytes -= copyLength;
  232. Debug.Assert(offset <= length);
  233. if (copyLength < remainingLength)
  234. {
  235. // update the position within the current block
  236. _position += copyLength;
  237. // this should be always the last iteration
  238. Debug.Assert(offset == length);
  239. }
  240. else
  241. {
  242. // the chunk is finished - remove it
  243. _chunks.RemoveFirst();
  244. if (_chunks.First != null)
  245. {
  246. // there is successor to this chunk
  247. _position = _chunks.First.Value.Offset;
  248. }
  249. else
  250. {
  251. // there is no successor to this chunk
  252. _position = -1;
  253. // this should be always the last iteration
  254. Debug.Assert(offset == length);
  255. }
  256. }
  257. }
  258. return block;
  259. }
  260. }
  261. /// <summary>
  262. /// Skips the given count of bytes in the queue.
  263. /// </summary>
  264. /// <param name="length">Number of bytes to skip.</param>
  265. /// <returns>True if the given count of bytes was available, otherwise false.</returns>
  266. public bool SkipByteBlock(int length)
  267. {
  268. Debug.Assert(length >= 0);
  269. if (length == 0)
  270. {
  271. return true;
  272. }
  273. else if (_availableBytes < length)
  274. {
  275. // cannot advance that much
  276. return false;
  277. }
  278. else
  279. {
  280. int alreadySkipped = 0;
  281. while (alreadySkipped < length)
  282. {
  283. // there should always be chunk available
  284. Debug.Assert(_chunks.First != null);
  285. Chunk chunk = _chunks.First.Value;
  286. int remainingLength = chunk.Offset + chunk.Length - _position;
  287. int advanceLength = length - alreadySkipped < remainingLength ? length - alreadySkipped : remainingLength;
  288. // position should be inside valid area of the chunk and not past the end
  289. Debug.Assert(_position >= chunk.Offset && _position < chunk.Offset + chunk.Length);
  290. // available bytes should be higher than chunk's remaining bytes
  291. Debug.Assert(_availableBytes >= remainingLength);
  292. // count to skip should be positive and not higher than chunk's remaining bytes
  293. Debug.Assert(advanceLength > 0 && advanceLength <= remainingLength);
  294. // update skipped count and available bytes
  295. alreadySkipped += advanceLength;
  296. _availableBytes -= advanceLength;
  297. Debug.Assert(alreadySkipped <= length);
  298. if (advanceLength < remainingLength)
  299. {
  300. // update the position within the current block
  301. _position += advanceLength;
  302. // this should be always the last iteration
  303. Debug.Assert(alreadySkipped == length);
  304. }
  305. else
  306. {
  307. // the chunk is finished - remove it
  308. _chunks.RemoveFirst();
  309. if (_chunks.First != null)
  310. {
  311. // there is successor to this chunk
  312. _position = _chunks.First.Value.Offset;
  313. }
  314. else
  315. {
  316. // there is no successor to this chunk
  317. _position = -1;
  318. // this should be always the last iteration
  319. Debug.Assert(alreadySkipped == length);
  320. }
  321. }
  322. }
  323. return true;
  324. }
  325. }
  326. /// <summary>
  327. /// Initializes new instance of the class.
  328. /// </summary>
  329. public BinaryChunkQueue()
  330. {
  331. _chunks = new LinkedList<Chunk>();
  332. _availableBytes = 0;
  333. _position = -1;
  334. }
  335. }
  336. }