2 * Copyright (C) 2013 FooProject
3 * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
11 #region Using Directives
14 using System.Collections.Generic;
15 using System.Collections;
17 using System.Threading;
18 using System.Globalization;
19 using System.Diagnostics;
21 #endregion Using Directives
24 namespace Slusser.Collections.Generic
27 /// Represents a strongly typed collection of objects that can be accessed by index. Insertions and
28 /// deletions to the collection near the same relative index are optimized.
30 /// <typeparam name="T">The type of elements in the buffer.</typeparam>
31 [DebuggerDisplay("Count = {Count}")]
32 [DebuggerTypeProxy(typeof(CollectionDebugView<>))]
33 sealed partial class GapBuffer<T> : IList<T>, IList
37 private const int MIN_CAPACITY = 4;
40 private int _gapStart;
44 private object _syncRoot;
52 /// Initializes a new instance of the <see cref="GapBuffer{T}"/> class.
56 this._buffer = new T[MIN_CAPACITY];
57 this._gapEnd = this._buffer.Length;
60 #endregion Constructors
66 /// Gets or sets the total number of elements the internal data structure can hold
69 /// <value>The number of elements that the <see cref="GapBuffer{T}"/> can contain before
70 /// resizing is required.</value>
71 /// <exception cref="ArgumentOutOfRangeException">
72 /// <see cref="Capacity"/> is set to a value that is less than <see cref="Count"/>.
78 return this._buffer.Length;
82 // Is there any work to do?
83 if (value == this._buffer.Length)
86 // Look for naughty boys and girls
88 throw new ArgumentOutOfRangeException("value", "");
92 // Allocate a new buffer
93 T[] newBuffer = new T[value];
94 int newGapEnd = newBuffer.Length - (this._buffer.Length - this._gapEnd);
96 // Copy the spans into the front and back of the new buffer
97 Array.Copy(this._buffer, 0, newBuffer, 0, this._gapStart);
98 Array.Copy(this._buffer, this._gapEnd, newBuffer, newGapEnd, newBuffer.Length - newGapEnd);
99 this._buffer = newBuffer;
100 this._gapEnd = newGapEnd;
105 this._buffer = new T[MIN_CAPACITY];
107 this._gapEnd = this._buffer.Length;
114 /// Gets the number of elements actually contained in the <see cref="GapBuffer{T}"/>.
117 /// The number of elements actually contained in the <see cref="GapBuffer{T}"/>.
123 return this._buffer.Length - (this._gapEnd - this._gapStart);
128 // Explicit IList implementation
129 bool IList.IsFixedSize
131 get { return false; }
135 // Explicit IList implementation
136 bool IList.IsReadOnly
138 get { return false; }
142 // Explicit ICollection<T> implementation
143 bool ICollection<T>.IsReadOnly
145 get { return false; }
149 // Explicit ICollection implementation
150 bool ICollection.IsSynchronized
152 get { return false; }
156 // Explicit ICollection implementation
157 object ICollection.SyncRoot
161 if (this._syncRoot == null)
162 Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
164 return this._syncRoot;
170 /// Gets or sets the element at the specified index.
172 /// <param name="index">The zero-based index of the element to get or set.</param>
173 /// <value>The element at the specified index.</value>
174 /// <exception cref="ArgumentOutOfRangeException">
175 /// <paramref name="index"/> is less than 0.
176 /// <para>-or-</para>
177 /// <paramref name="index"/> is equal to or greater than <see cref="Count"/>.
179 public T this[int index]
183 if (index < 0 || index >= Count)
184 throw new ArgumentOutOfRangeException("index", "");
186 // Find the correct span and get the item
187 if (index >= this._gapStart)
188 index += (this._gapEnd - this._gapStart);
190 return this._buffer[index];
194 if (index < 0 || index >= Count)
195 throw new ArgumentOutOfRangeException("index", "");
197 // Find the correct span and set the item
198 if (index >= this._gapStart)
199 index += (this._gapEnd - this._gapStart);
201 this._buffer[index] = value;
207 // Explicit IList implementation
208 object IList.this[int index]
210 get { return this[index]; }
213 VerifyValueType(value);
214 this[index] = (T)value;
218 #endregion Properties
224 /// Adds an object to the end of the <see cref="GapBuffer{T}"/>.
226 /// <param name="item">The object to be added to the end of the <see cref="GapBuffer{T}"/>.
227 /// The value can be null for reference types.</param>
228 public void Add(T item)
234 // Explicit IList implementation
235 int IList.Add(object value)
237 VerifyValueType(value);
244 /// Adds the elements of the specified collection to the end of the <see cref="GapBuffer{T}"/>.
246 /// <param name="collection">The collection whose elements should be inserted into the <see cref="GapBuffer{T}"/>.
247 /// The collection itself cannot be null, but it can contain elements that are null, if
248 /// type <typeparamref name="T"/> is a reference type.</param>
249 /// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference.</exception>
250 public void AddRange(IEnumerable<T> collection)
252 InsertRange(Count, collection);
257 /// Removes all elements from the <see cref="GapBuffer{T}"/>.
261 // Clearing the buffer means simply enlarging the gap to the
262 // entire buffer size
264 Array.Clear(this._buffer, 0, this._buffer.Length);
266 this._gapEnd = this._buffer.Length;
272 /// Determines whether an element is in the <see cref="GapBuffer{T}"/>.
274 /// <param name="item">The object to locate in the <see cref="GapBuffer{T}"/>. The value
275 /// can be null for reference types.</param>
276 /// <returns><b>true</b> if item is found in the <see cref="GapBuffer{T}"/>;
277 /// otherwise, <b>false</b>.</returns>
278 public bool Contains(T item)
280 // Search on both sides of the gap for the item
282 EqualityComparer<T> comparer = EqualityComparer<T>.Default;
283 for (int i = 0; i < this._gapStart; i++)
285 if (comparer.Equals(this._buffer[i], item))
288 for (int i = this._gapEnd; i < this._buffer.Length; i++)
290 if (comparer.Equals(this._buffer[i], item))
298 // Explicit IList implementation
299 bool IList.Contains(object value)
301 if (IsCompatibleObject(value))
302 return Contains((T)value);
309 /// Copies the <see cref="GapBuffer{T}"/> to a compatible one-dimensional array,
310 /// starting at the specified index of the target array.
312 /// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements
313 /// copied from <see cref="GapBuffer{T}"/>. The <see cref="Array"/> must have zero-based indexing.</param>
314 /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
315 /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference.</exception>
316 /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception>
317 /// <exception cref="ArgumentException">
318 /// <paramref name="array"/> is multidimensional.
319 /// <para>-or-</para>
320 /// <paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>.
321 /// <para>-or-</para>
322 /// The number of elements in the source <see cref="GapBuffer{T}"/> is greater than the available space
323 /// from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.
325 public void CopyTo(T[] array, int arrayIndex)
328 throw new ArgumentNullException("array");
331 throw new ArgumentOutOfRangeException("arrayIndex", "");
334 throw new ArgumentException("", "array");
336 if (arrayIndex >= array.Length || arrayIndex + Count > array.Length)
337 throw new ArgumentException("", "arrayIndex");
340 // Copy the spans into the destination array at the offset
341 Array.Copy(this._buffer, 0, array, arrayIndex, this._gapStart);
342 Array.Copy(this._buffer, this._gapEnd, array, arrayIndex + this._gapStart, this._buffer.Length - this._gapEnd);
346 // Explicit ICollection implementation
347 void ICollection.CopyTo(Array array, int arrayIndex)
351 CopyTo((T[])array, arrayIndex);
353 catch (InvalidCastException)
355 throw new ArgumentException("", "array");
365 /// Returns an enumerator that iterates through the <see cref="GapBuffer{T}"/>.
367 /// <returns>A <see cref="GapBuffer{T}.Enumerator"/> for the <see cref="GapBuffer{T}"/>.</returns>
368 public GapBuffer<T>.Enumerator GetEnumerator()
370 return new GapBuffer<T>.Enumerator(this);
374 // Explicit IEnumerable implementation
375 IEnumerator IEnumerable.GetEnumerator()
377 return new GapBuffer<T>.Enumerator(this);
381 // Explicit IEnumerable<T> implementation
382 IEnumerator<T> IEnumerable<T>.GetEnumerator()
384 return new GapBuffer<T>.Enumerator(this);
389 /// Searches for the specified object and returns the zero-based index of the first
390 /// occurrence within the <see cref="GapBuffer{T}"/>.
392 /// <param name="item">The object to locate in the <see cref="GapBuffer{T}"/>. The value
393 /// can be null for reference types.</param>
394 /// <returns>The zero-based index of the first occurrence of <paramref name="item"/> within
395 /// the <see cref="GapBuffer{T}"/>, if found; otherwise,
\81E.</returns>
396 public int IndexOf(T item)
398 // Search within the buffer spans
400 int index = Array.IndexOf<T>(this._buffer, item, 0, this._gapStart);
403 index = Array.IndexOf<T>(this._buffer, item, this._gapEnd, this._buffer.Length - this._gapEnd);
405 // Translate the internal index to the index in the collection
407 return index - (this._gapEnd - this._gapStart);
414 // Explicit IList implementation
415 int IList.IndexOf(object item)
417 if (IsCompatibleObject(item))
418 return IndexOf((T)item);
425 /// Inserts an element into the <see cref="GapBuffer{T}"/> at the specified index. Consecutive operations
426 /// at or near previous inserts are optimized.
428 /// <param name="index">The object to insert. The value can be null for reference types.</param>
429 /// <param name="item">The zero-based index at which <paramref name="item"/> should be inserted.</param>
430 /// <exception cref="ArgumentOutOfRangeException">
431 /// <paramref name="index"/> is less than 0.
432 /// <para>-or-</para>
433 /// <paramref name="index"/> is greater than <see cref="Count"/>.
435 public void Insert(int index, T item)
437 if (index < 0 || index > Count)
438 throw new ArgumentOutOfRangeException("index", "");
440 // Prepare the buffer
441 PlaceGapStart(index);
442 EnsureGapCapacity(1);
444 this._buffer[index] = item;
450 // Explicit IList implementation
451 void IList.Insert(int index, object item)
453 VerifyValueType(item);
454 Insert(index, (T)item);
459 /// Inserts the elements of a collection into the <see cref="GapBuffer{T}"/> at the specified index.
460 /// Consecutive operations at or near previous inserts are optimized.
462 /// <param name="index">The zero-based index at which the new elements should be inserted.</param>
463 /// <param name="col">The collection whose elements should be inserted into the <see cref="GapBuffer{T}"/>.
464 /// The collection itself cannot be null, but it can contain elements that are null, if
465 /// type <typeparamref name="T"/> is a reference type.</param>
466 /// <exception cref="ArgumentNullException"><paramref name="col"/> is a null reference.</exception>
467 /// <exception cref="ArgumentOutOfRangeException">
468 /// <paramref name="index"/> is less than 0.
469 /// <para>-or-</para>
470 /// <paramref name="index"/> is greater than <see cref="Count"/>.
472 public void InsertRange(int index, ICollection<T> col)
475 throw new ArgumentNullException("collection");
477 if (index < 0 || index > Count)
478 throw new ArgumentOutOfRangeException("index", "");
482 int count = col.Count;
485 PlaceGapStart(index);
486 EnsureGapCapacity(count);
488 // Copy the collection directly into the buffer
489 col.CopyTo(this._buffer, this._gapStart);
490 this._gapStart += count;
496 /// Inserts the elements of a collection into the <see cref="GapBuffer{T}"/> at the specified index.
497 /// Consecutive operations at or near previous inserts are optimized.
499 /// <param name="index">The zero-based index at which the new elements should be inserted.</param>
500 /// <param name="collection">The collection whose elements should be inserted into the <see cref="GapBuffer{T}"/>.
501 /// The collection itself cannot be null, but it can contain elements that are null, if
502 /// type <typeparamref name="T"/> is a reference type.</param>
503 /// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference.</exception>
504 /// <exception cref="ArgumentOutOfRangeException">
505 /// <paramref name="index"/> is less than 0.
506 /// <para>-or-</para>
507 /// <paramref name="index"/> is greater than <see cref="Count"/>.
509 public void InsertRange(int index, IEnumerable<T> collection)
511 if (collection == null)
512 throw new ArgumentNullException("collection");
514 if (index < 0 || index > Count)
515 throw new ArgumentOutOfRangeException("index", "");
517 ICollection<T> col = collection as ICollection<T>;
520 int count = col.Count;
523 PlaceGapStart(index);
524 EnsureGapCapacity(count);
526 // Copy the collection directly into the buffer
527 col.CopyTo(this._buffer, this._gapStart);
528 this._gapStart += count;
533 // Add the items to the buffer one-at-a-time :(
534 using (IEnumerator<T> enumerator = collection.GetEnumerator())
536 while (enumerator.MoveNext())
538 Insert(index, enumerator.Current);
547 /// Removes the first occurrence of a specific object from the <see cref="GapBuffer{T}"/>.
549 /// <param name="item">The object to remove from the <see cref="GapBuffer{T}"/>. The
550 /// value can be null for reference types.</param>
551 /// <returns><b>true</b> if <paramref name="item"/> is successfully removed; otherwise,
552 /// <b>false</b>. This method also returns <b>false</b> if <paramref name="item"/> was not
553 /// found in the <see cref="GapBuffer{T}"/>.</returns>
554 public bool Remove(T item)
556 // Get the index of the item
557 int index = IndexOf(item);
567 // Explicit IList implementation
568 void IList.Remove(object item)
570 if (IsCompatibleObject(item))
576 /// Removes the element at the specified index of the <see cref="GapBuffer{T}"/>.
578 /// <param name="index">The zero-based index of the element to remove.</param>
579 /// <exception cref="ArgumentOutOfRangeException">
580 /// <paramref name="index"/> is less than 0.
581 /// <para>-or-</para>
582 /// <paramref name="index"/> is equal to or greater than <see cref="Count"/>.
584 public void RemoveAt(int index)
586 if (index < 0 || index >= Count)
587 throw new ArgumentOutOfRangeException("index", "");
589 // Place the gap at the index and increase the gap size by 1
590 PlaceGapStart(index);
591 this._buffer[this._gapEnd] = default(T);
598 /// Removes a range of elements from the <see cref="GapBuffer{T}"/>.
600 /// <param name="index">The zero-based starting index of the range of elements to remove.</param>
601 /// <param name="count">The number of elements to remove.</param>
602 /// <exception cref="ArgumentOutOfRangeException">
603 /// <paramref name="index"/> is less than 0 or is equal to or greater than <see cref="Count"/>.
604 /// <para>-or-</para>
605 /// <paramref name="count"/> is less than 0.
606 /// <para>-or-</para>
607 /// <paramref name="index"/> and <paramref name="count"/> do not denote a valid range of elements in
608 /// the <see cref="GapBuffer{T}"/>.
610 public void RemoveRange(int index, int count)
614 if (index < 0 || index >= size)
615 throw new ArgumentOutOfRangeException("index", "");
617 if (count < 0 || size - index < count)
618 throw new ArgumentOutOfRangeException("count", "");
621 // Move the gap over the index and increase the gap size
622 // by the number of elements removed. Easy as pie!
626 PlaceGapStart(index);
627 //Array.Clear(this._buffer, this._gapEnd, count);
628 this._gapEnd += count;
635 /// Sets the <see cref="Capacity"/> to the actual number of elements in the <see cref="GapBuffer{T}"/>,
636 /// if that number is less than a threshold value.
638 public void TrimExcess()
641 int threshold = (int)(_buffer.Length * 0.9);
642 if (size < threshold)
649 // Moves the gap start to the given index
650 private void PlaceGapStart(int index)
652 // Are we already there?
653 if (index == this._gapStart)
656 // Is there even a gap?
657 if ((this._gapEnd - this._gapStart) == 0)
659 this._gapStart = index;
660 this._gapEnd = index;
664 // Which direction do we move the gap?
665 if (index < this._gapStart)
667 // Move the gap near (by copying the items at the beginning
668 // of the gap to the end)
669 int count = this._gapStart - index;
670 int deltaCount = (this._gapEnd - this._gapStart < count ? this._gapEnd - this._gapStart : count);
671 Array.Copy(this._buffer, index, this._buffer, this._gapEnd - count, count);
672 this._gapStart -= count;
673 this._gapEnd -= count;
675 // Clear the contents of the gap
676 //Array.Clear(this._buffer, index, deltaCount);
680 // Move the gap far (by copying the items at the end
681 // of the gap to the beginning)
682 int count = index - this._gapStart;
683 int deltaIndex = (index > this._gapEnd ? index : this._gapEnd);
684 Array.Copy(this._buffer, this._gapEnd, this._buffer, this._gapStart, count);
685 this._gapStart += count;
686 this._gapEnd += count;
688 // Clear the contents of the gap
689 //Array.Clear(this._buffer, deltaIndex, this._gapEnd - deltaIndex);
694 // Expands the interal array if the required size isn't available
695 private void EnsureGapCapacity(int required)
697 // Is the available space in the gap?
698 if (required > (this._gapEnd - this._gapStart))
700 // Calculate a new size (double the size necessary)
701 int newCapacity = (Count + required) * 2;
702 if (newCapacity < MIN_CAPACITY)
703 newCapacity = MIN_CAPACITY;
705 Capacity = newCapacity;
710 private static bool IsCompatibleObject(object value)
712 // Ensure the object is compatible with the generic type
714 if (!(value is T) && ((value != null) || value is ValueType))
721 private static void VerifyValueType(object value)
723 // Throw an exception if the object is not compatible with
726 if (!IsCompatibleObject(value))
728 throw new ArgumentException("", "value");