// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections; using System.Collections.Generic; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.Utility { /// /// Collection class that permits storage of over int.MaxValue items. This is performed /// by using a 2D list of lists. The internal lists are only initialized as necessary. This /// collection implements IEnumerable to make it easier to run LINQ queries against it. /// /// /// This class is based on code from $\Data Tools\SSMS_Main\sql\ssms\core\DataStorage\ArrayList64.cs /// with additions to bring it up to .NET 4.5 standards /// /// Type of the values to store public class LongList : IEnumerable { #region Member Variables private List> expandedList; private readonly List shortList; #endregion /// /// Creates a new long list /// public LongList() { shortList = new List(); ExpandListSize = int.MaxValue; Count = 0; } #region Properties /// /// The total number of elements in the array /// public long Count { get; private set; } /// /// Used to get or set the value at a given index in the list /// /// Index into the list to access public T this[long index] { get { return GetItem(index); } set { SetItem(index, value); } } /// /// The number of elements to store in a single list before expanding into multiple lists /// public int ExpandListSize { get; internal set; } #endregion #region Public Methods /// /// Adds the specified value to the end of the list /// /// Value to add to the list /// Index of the item that was just added public long Add(T val) { if (Count < this.ExpandListSize) { shortList.Add(val); } else // need to split values into several arrays { if (expandedList == null) { // very inefficient so delay as much as possible // immediately add 0th array expandedList = new List> {shortList}; } int arrayIndex = (int)(Count / this.ExpandListSize); // 0 based List arr; if (expandedList.Count <= arrayIndex) // need to make a new array { arr = new List(); expandedList.Add(arr); } else // use existing array { arr = expandedList[arrayIndex]; } arr.Add(val); } return (++Count); } /// /// Returns the item at the specified index /// /// Index of the item to return /// The item at the index specified public T GetItem(long index) { Validate.IsWithinRange(nameof(index), index, 0, Count - 1); T val = default(T); if (Count < this.ExpandListSize) { int i32Index = Convert.ToInt32(index); val = shortList[i32Index]; } else { int iArray32Index = (int) (index / this.ExpandListSize); if (expandedList.Count > iArray32Index) { List arr = expandedList[iArray32Index]; int i32Index = (int) (index % this.ExpandListSize); if (arr.Count > i32Index) { val = arr[i32Index]; } } } return val; } /// /// Skips ahead the number of elements requested and returns the elements after that many elements /// /// The number of elements to skip /// All elements after the number of elements to skip public IEnumerable LongSkip(long start) { Validate.IsWithinRange(nameof(start), start, 0, Count - 1); // Generate an enumerator over this list and jump ahead to the position we want LongListEnumerator longEnumerator = new LongListEnumerator(this) {Index = start - 1}; // While there are results to get, yield return them while (longEnumerator.MoveNext()) { yield return longEnumerator.Current; } } /// /// Sets the item at the specified index /// /// Index of the item to set /// The item to store at the index specified public void SetItem(long index, T value) { Validate.IsLessThan(nameof(index), index, Count); if (Count <= this.ExpandListSize) { int i32Index = Convert.ToInt32(index); shortList[i32Index] = value; } else { int iArray32Index = (int) (index / this.ExpandListSize); List arr = expandedList[iArray32Index]; int i32Index = (int)(index % this.ExpandListSize); arr[i32Index] = value; } } /// /// Removes an item at the specified location and shifts all the items after the provided /// index up by one. /// /// The index to remove from the list public void RemoveAt(long index) { Validate.IsWithinRange(nameof(index), index, 0, Count - 1); if (Count <= this.ExpandListSize) { int iArray32MemberIndex = Convert.ToInt32(index); // 0 based shortList.RemoveAt(iArray32MemberIndex); } else // handle the case of multiple arrays { // find out which array it is in int arrayIndex = (int) (index / this.ExpandListSize); List arr = expandedList[arrayIndex]; // find out index into this array int iArray32MemberIndex = (int) (index % this.ExpandListSize); arr.RemoveAt(iArray32MemberIndex); // now shift members of the array back one //int iArray32TotalIndex = (int) (Count / this.ExpandListSize); for (int i = arrayIndex + 1; i < expandedList.Count; i++) { List arr1 = expandedList[i - 1]; List arr2 = expandedList[i]; arr1.Add(arr2[0]); arr2.RemoveAt(0); if (arr2.Count == 0) { expandedList.RemoveAt(i); } } } --Count; } #endregion #region IEnumerable Implementation /// /// Returns a generic enumerator for enumeration of this LongList /// /// Enumerator for LongList public IEnumerator GetEnumerator() { return new LongListEnumerator(this); } /// /// Returns an enumerator for enumeration of this LongList /// /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion public class LongListEnumerator : IEnumerator { /// /// The current list that we're iterating over. /// private readonly LongList localList; /// /// The current index into the list /// private long index; /// /// Constructs a new enumerator for a given LongList /// /// The list to enumerate public LongListEnumerator(LongList list) { localList = list; index = -1; Current = default(TEt); } /// /// The index into the list of the item that is the current item. Upon setting, /// will be updated if the index is in range. Otherwise, /// default() will be used. /// public long Index { get { return index; } set { index = value; Current = value >= localList.Count || value < 0 ? default(TEt) : localList[index]; } } #region IEnumerator Implementation /// /// Returns the current item in the enumeration /// public TEt Current { get; private set; } /// /// Returns the current item in the enumeration /// object IEnumerator.Current => Current; /// /// Moves to the next item in the list we're iterating over /// /// Whether or not the move was successful public bool MoveNext() { Index++; return Index < localList.Count; } /// /// Resets the enumeration /// public void Reset() { Index = 0; Current = default(TEt); } /// /// Disposal method. Does nothing. /// public void Dispose() { } #endregion } } }