// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; namespace ICSharpCode.SharpDevelop.Dom { /// /// Provides LINQ operators for . /// public static class ModelCollectionLinq { // A general 'AsObservableCollection()' would be nice; but I don't see any good way // to implement that without leaking memory. // The problem is that IModelCollection is unordered; but ObservableCollection requires us to maintain a stable order. #region OfType / Cast public static IModelCollection OfType(this IModelCollection source) { return SelectMany(source, item => item is TResult ? new ImmutableModelCollection(new[] { (TResult)item }) : ImmutableModelCollection.Empty); } public static IModelCollection Cast(this IModelCollection source) { return SelectMany(source, item => new ImmutableModelCollection(new[] { (TResult)item })); } #endregion #region Where public static IModelCollection Where(this IModelCollection source, Func predicate) { return SelectMany(source, item => predicate(item) ? new ImmutableModelCollection(new[] { item }) : ImmutableModelCollection.Empty); } #endregion #region Select public static IModelCollection Select(this IModelCollection source, Func selector) { return SelectMany(source, item => new ImmutableModelCollection(new[] { selector(item) })); } #endregion #region SelectMany public static IModelCollection SelectMany(this IModelCollection input, Func> selector) { return SelectMany(input, selector, (a, b) => b); } public static IModelCollection SelectMany(this IModelCollection source, Func> collectionSelector, Func resultSelector) { if (source == null) throw new ArgumentNullException("source"); if (collectionSelector == null) throw new ArgumentNullException("collectionSelector"); if (resultSelector == null) throw new ArgumentNullException("resultSelector"); return new SelectManyModelCollection(source, collectionSelector, resultSelector); } sealed class SelectManyModelCollection : IModelCollection { readonly IModelCollection source; readonly Func> collectionSelector; readonly Func resultSelector; List inputCollections; public SelectManyModelCollection(IModelCollection source, Func> collectionSelector, Func resultSelector) { this.source = source; this.collectionSelector = collectionSelector; this.resultSelector = resultSelector; } ModelCollectionChangedEventHandler collectionChanged; public event ModelCollectionChangedEventHandler CollectionChanged { add { if (value == null) return; if (inputCollections == null) { source.CollectionChanged += OnSourceCollectionChanged; inputCollections = new List(); foreach (var item in source) { var inputCollection = new InputCollection(this, item); inputCollection.RegisterEvent(); inputCollections.Add(inputCollection); } } collectionChanged += value; } remove { if (collectionChanged == null) return; collectionChanged -= value; if (collectionChanged == null) { source.CollectionChanged -= OnSourceCollectionChanged; foreach (var inputCollection in inputCollections) { inputCollection.UnregisterEvent(); } inputCollections = null; } } } void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { if (collectionChanged != null) collectionChanged(removedItems, addedItems); } void OnSourceCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { List removedResults = new List(); foreach (TSource removedSource in removedItems) { for (int i = 0; i < inputCollections.Count; i++) { var inputCollection = inputCollections[i]; if (EqualityComparer.Default.Equals(inputCollection.source, removedSource)) { inputCollection.AddResultsToList(removedResults); inputCollection.UnregisterEvent(); inputCollections.RemoveAt(i); break; } } } List addedResults = new List(); foreach (TSource addedSource in addedItems) { var inputCollection = new InputCollection(this, addedSource); inputCollection.AddResultsToList(addedResults); inputCollection.RegisterEvent(); inputCollections.Add(inputCollection); } OnCollectionChanged(removedResults, addedResults); } class InputCollection { readonly SelectManyModelCollection parent; internal readonly TSource source; readonly IModelCollection collection; public InputCollection(SelectManyModelCollection parent, TSource source) { this.parent = parent; this.source = source; this.collection = parent.collectionSelector(source); } public void AddResultsToList(List output) { foreach (var item in collection) { output.Add(parent.resultSelector(source, item)); } } public void RegisterEvent() { collection.CollectionChanged += OnCollectionChanged; } public void UnregisterEvent() { collection.CollectionChanged -= OnCollectionChanged; } IReadOnlyCollection GetResults(IReadOnlyCollection itemsCollection) { List results = new List(itemsCollection.Count); foreach (var item in itemsCollection) { results.Add(parent.resultSelector(source, item)); } return results; } void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { parent.OnCollectionChanged(GetResults(removedItems), GetResults(addedItems)); } } IReadOnlyCollection IModelCollection.CreateSnapshot() { return this.ToList(); } public IEnumerator GetEnumerator() { return source.AsEnumerable().SelectMany(collectionSelector, resultSelector).GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } public int Count { get { return source.Sum(c => collectionSelector(c).Count); } } } #endregion } }