public void AddRange()
        {
            _source.AddRange(Enumerable.Range(1, 10));

            var changes = _source.CaptureChanges();

            _clone.Clone(changes);
            CollectionAssert.AreEqual(_source, _clone);
        }
示例#2
0
        private void Requery(Func <T, bool> predicate, List <ItemWithMatch> all, ChangeAwareList <ItemWithMatch> filtered)
        {
            if (_policy == ListFilterPolicy.ClearAndReplace)
            {
                var newMatches = all.Where(iwm => predicate(iwm.Item)).ToList();

                //mark items as matched?
                filtered.Clear();
                filtered.AddRange(newMatches);

                //reset state
                all.Where(iwm => iwm.IsMatch).ForEach(iwm => iwm.IsMatch = false);
                newMatches.ForEach(iwm => iwm.IsMatch = true);

                return;
            }


            var mutatedMatches = new List <Action>(all.Count);

            var newState = all.Select(item =>
            {
                var match    = predicate(item.Item);
                var wasMatch = item.IsMatch;

                //Mutate match - defer until filtered list has been modified
                //[to prevent potential IndexOf failures]
                if (item.IsMatch != match)
                {
                    mutatedMatches.Add(() => item.IsMatch = match);
                }

                return(new
                {
                    Item = item,
                    IsMatch = match,
                    WasMatch = wasMatch
                });
            }).ToList();

            //reflect items which are no longer matched
            var noLongerMatched = newState.Where(state => !state.IsMatch && state.WasMatch).Select(state => state.Item);

            filtered.RemoveMany(noLongerMatched);

            //reflect new matches in the list
            var newMatched = newState.Where(state => state.IsMatch && !state.WasMatch).Select(state => state.Item);

            filtered.AddRange(newMatched);

            //finally apply mutations
            mutatedMatches.ForEach(m => m());
        }
示例#3
0
        public void AddRange()
        {
            _list.AddRange(Enumerable.Range(1, 10));

            //assert changes
            var changes = _list.CaptureChanges();

            changes.Count.Should().Be(1);
            changes.Adds.Should().Be(10);
            changes.First().Range.ShouldAllBeEquivalentTo(Enumerable.Range(1, 10));

            //assert collection
            _list.ShouldAllBeEquivalentTo(Enumerable.Range(1, 10));
        }
        public void AddRange()
        {
            _list.AddRange(Enumerable.Range(1, 10));

            //assert changes
            var changes = _list.CaptureChanges();

            Assert.AreEqual(1, changes.Count);
            Assert.AreEqual(10, changes.Adds);
            CollectionAssert.AreEqual(Enumerable.Range(1, 10), changes.First().Range);

            //assert collection
            CollectionAssert.AreEqual(Enumerable.Range(1, 10), _list);
        }
示例#5
0
        private void Requery(Func<T, bool> predicate, List<ItemWithMatch> all, ChangeAwareList<ItemWithMatch> filtered)
        {
            var mutatedMatches = new List<Action>(all.Count);

            var newState = all.Select(item =>
            {
                var match = predicate(item.Item);
                var wasMatch = item.IsMatch;

                //Mutate match - defer until filtered list has been modified
                //[to prevent potential IndexOf failures]
                if (item.IsMatch != match)
                    mutatedMatches.Add(()=> item.IsMatch = match);

                return new
                {
                    Item = item,
                    IsMatch = match,
                    WasMatch = wasMatch
                };
            }).ToList();

            //reflect items which are no longer matched
            var noLongerMatched = newState.Where(state => !state.IsMatch && state.WasMatch).Select(state => state.Item);
            filtered.RemoveMany(noLongerMatched);

            //reflect new matches in the list
            var newMatched = newState.Where(state => state.IsMatch && !state.WasMatch).Select(state => state.Item);
            filtered.AddRange(newMatched);

            //finally apply mutations
            mutatedMatches.ForEach(m => m());
        }
示例#6
0
        private void Process(ChangeAwareList <T> filtered, IChangeSet <T> changes)
        {
            foreach (var item in changes)
            {
                switch (item.Reason)
                {
                case ListChangeReason.Add:
                {
                    var change = item.Item;
                    if (_predicate(change.Current))
                    {
                        filtered.Add(change.Current);
                    }

                    break;
                }

                case ListChangeReason.AddRange:
                {
                    var matches = item.Range.Where(t => _predicate(t)).ToList();
                    filtered.AddRange(matches);
                    break;
                }

                case ListChangeReason.Replace:
                {
                    var change = item.Item;
                    var match  = _predicate(change.Current);
                    if (match)
                    {
                        filtered.ReplaceOrAdd(change.Previous.Value, change.Current);
                    }
                    else
                    {
                        filtered.Remove(change.Previous.Value);
                    }

                    break;
                }

                case ListChangeReason.Remove:
                {
                    filtered.Remove(item.Item.Current);
                    break;
                }

                case ListChangeReason.RemoveRange:
                {
                    filtered.RemoveMany(item.Range);
                    break;
                }

                case ListChangeReason.Clear:
                {
                    filtered.ClearOrRemoveMany(item);
                    break;
                }
                }
            }
        }
        public IObservable <IChangeSet <T> > Run()
        {
            return(Observable.Create <IChangeSet <T> >(observer =>
            {
                long orderItemWasAdded = -1;
                var locker = new object();

                var sourceList = new ChangeAwareList <ExpirableItem <T> >();

                var sizeLimited = _source.Synchronize(locker)
                                  .Scan(sourceList, (state, latest) =>
                {
                    var items = latest.AsArray();
                    var expirable = items.Select(t => CreateExpirableItem(t, ref orderItemWasAdded));

                    if (items.Length == 1)
                    {
                        sourceList.Add(expirable);
                    }
                    else
                    {
                        sourceList.AddRange(expirable);
                    }

                    if (_limitSizeTo > 0 && state.Count > _limitSizeTo)
                    {
                        //remove oldest items [these will always be the first x in the list]
                        var toRemove = state.Count - _limitSizeTo;
                        state.RemoveRange(0, toRemove);
                    }
                    return state;
                })
                                  .Select(state => state.CaptureChanges())
                                  .Publish();

                var timeLimited = (_expireAfter == null ? Observable.Never <IChangeSet <ExpirableItem <T> > >() : sizeLimited)
                                  .Filter(ei => ei.ExpireAt != DateTime.MaxValue)
                                  .GroupWithImmutableState(ei => ei.ExpireAt)
                                  .MergeMany(grouping =>
                {
                    var expireAt = grouping.Key.Subtract(_scheduler.Now.DateTime);
                    return Observable.Timer(expireAt, _scheduler).Select(_ => grouping);
                })
                                  .Synchronize(locker)
                                  .Select(grouping =>
                {
                    sourceList.RemoveMany(grouping.Items);
                    return sourceList.CaptureChanges();
                });

                var publisher = sizeLimited
                                .Merge(timeLimited)
                                .Cast(ei => ei.Item)
                                .NotEmpty()
                                .SubscribeSafe(observer);

                return new CompositeDisposable(publisher, sizeLimited.Connect());
            }));
        }
示例#8
0
        private IChangeSet <T> Reset(List <T> original, ChangeAwareList <T> target)
        {
            var sorted = original.OrderBy(t => t, _comparer).ToList();

            target.Clear();
            target.AddRange(sorted);
            return(target.CaptureChanges());
        }
示例#9
0
        private IChangeSet <ItemWithMatch> Requery(Func <T, bool> predicate, List <ItemWithMatch> all, ChangeAwareList <ItemWithMatch> filtered)
        {
            if (all.Count == 0)
            {
                return(ChangeSet <ItemWithMatch> .Empty);
            }

            if (_policy == ListFilterPolicy.ClearAndReplace)
            {
                var itemsWithMatch = all.Select(iwm => new ItemWithMatch(iwm.Item, predicate(iwm.Item), iwm.IsMatch)).ToList();

                //mark items as matched?
                filtered.Clear();
                filtered.AddRange(itemsWithMatch.Where(iwm => iwm.IsMatch));

                //reset state for all items
                all.Clear();
                all.AddRange(itemsWithMatch);
                return(filtered.CaptureChanges());
            }

            var toAdd    = new List <ItemWithMatch>(all.Count);
            var toRemove = new List <ItemWithMatch>(all.Count);

            for (int i = 0; i < all.Count; i++)
            {
                var original = all[i];

                var newItem = new ItemWithMatch(original.Item, predicate(original.Item), original.IsMatch);
                all[i] = newItem;

                if (newItem.IsMatch && !newItem.WasMatch)
                {
                    toAdd.Add(newItem);
                }
                else if (!newItem.IsMatch && newItem.WasMatch)
                {
                    toRemove.Add(newItem);
                }
            }

            filtered.RemoveMany(toRemove);
            filtered.AddRange(toAdd);

            return(filtered.CaptureChanges());
        }
示例#10
0
        private IChangeSet <T> ChangeComparer(ChangeAwareList <T> target, IComparer <T> comparer)
        {
            _comparer = comparer;
            var sorted = target.OrderBy(t => t, _comparer).ToList();

            target.Clear();
            target.AddRange(sorted);
            return(target.CaptureChanges());
        }
示例#11
0
        private IChangeSet <T> ChangeComparer(ChangeAwareList <T> target, IComparer <T> comparer)
        {
            _comparer = comparer;
            if (_resetThreshold > 0 && target.Count <= _resetThreshold)
            {
                return(Reorder(target));
            }

            var sorted = target.OrderBy(t => t, _comparer).ToList();

            target.Clear();
            target.AddRange(sorted);
            return(target.CaptureChanges());
        }
示例#12
0
        private IChangeSet <T> ProcessImpl(ChangeAwareList <T> target, IChangeSet <T> changes)
        {
            var refreshes = new List <T>(changes.Refreshes);

            foreach (var change in changes)
            {
                switch (change.Reason)
                {
                case ListChangeReason.Add:
                {
                    var current = change.Item.Current;
                    Insert(target, current);
                    break;
                }

                case ListChangeReason.AddRange:
                {
                    var ordered = change.Range.OrderBy(t => t, _comparer).ToList();
                    if (target.Count == 0)
                    {
                        target.AddRange(ordered);
                    }
                    else
                    {
                        ordered.ForEach(item => Insert(target, item));
                    }

                    break;
                }

                case ListChangeReason.Remove:
                {
                    var current = change.Item.Current;
                    Remove(target, current);
                    break;
                }

                case ListChangeReason.Refresh:
                {
                    // add to refresh list so position can be calculated
                    refreshes.Add(change.Item.Current);

                    // add to current list so downstream operators can receive a refresh
                    // notification, so get the latest index and pass the index up the chain
                    var indexed = target.IndexOfOptional(change.Item.Current).ValueOrThrow(() => new SortException($"Cannot find index of {typeof(T).Name} -> {change.Item.Current}. Expected to be in the list"));

                    target.Refresh(indexed.Item, indexed.Index);
                    break;
                }

                case ListChangeReason.Replace:
                {
                    var current = change.Item.Current;

                    // TODO: check whether an item should stay in the same position
                    // i.e. update and move
                    Remove(target, change.Item.Previous.Value);
                    Insert(target, current);
                    break;
                }

                case ListChangeReason.RemoveRange:
                {
                    target.RemoveMany(change.Range);
                    break;
                }

                case ListChangeReason.Clear:
                {
                    target.Clear();
                    break;
                }
                }
            }

            // Now deal with refreshes [can be expensive]
            foreach (var item in refreshes)
            {
                var old = target.IndexOf(item);
                if (old == -1)
                {
                    continue;
                }

                int newPosition = GetInsertPositionLinear(target, item);
                if (old < newPosition)
                {
                    newPosition--;
                }

                if (old == newPosition)
                {
                    continue;
                }

                target.Move(old, newPosition);
            }

            return(target.CaptureChanges());
        }
示例#13
0
        private IChangeSet <T> ProcessImpl(ChangeAwareList <T> target, IChangeSet <T> changes)
        {
            if (_comparer == null)
            {
                target.Clone(changes);
                return(target.CaptureChanges());
            }

            foreach (var change in changes)
            {
                switch (change.Reason)
                {
                case ListChangeReason.Add:
                {
                    var current = change.Item.Current;
                    Insert(target, current);
                    break;
                }

                case ListChangeReason.AddRange:
                {
                    var ordered = change.Range.OrderBy(t => t, _comparer).ToList();
                    if (target.Count == 0)
                    {
                        target.AddRange(ordered);
                    }
                    else
                    {
                        ordered.ForEach(item => Insert(target, item));
                    }
                    break;
                }

                case ListChangeReason.Replace:
                {
                    var current = change.Item.Current;
                    //TODO: check whether an item should stay in the same position
                    //i.e. update and move
                    Remove(target, change.Item.Previous.Value);
                    Insert(target, current);
                    break;
                }

                case ListChangeReason.Remove:
                {
                    var current = change.Item.Current;
                    Remove(target, current);
                    break;
                }

                case ListChangeReason.RemoveRange:
                {
                    target.RemoveMany(change.Range);
                    break;
                }

                case ListChangeReason.Clear:
                {
                    target.Clear();
                    break;
                }
                }
            }
            return(target.CaptureChanges());
        }
示例#14
0
        public void AddRange()
        {
            _source.AddRange(Enumerable.Range(1, 10));

            var changes = _source.CaptureChanges();

            _clone.Clone(changes);
            _clone.Should().BeEquivalentTo(_source);
        }
示例#15
0
        private void Process(ChangeAwareList <ItemWithMatch> filtered, IChangeSet <ItemWithMatch> changes)
        {
            //Maintain all items as well as filtered list. This enables us to a) requery when the predicate changes b) check the previous state when Refresh is called
            foreach (var item in changes)
            {
                switch (item.Reason)
                {
                case ListChangeReason.Add:
                {
                    var change = item.Item;
                    if (change.Current.IsMatch)
                    {
                        filtered.Add(change.Current);
                    }
                    break;
                }

                case ListChangeReason.AddRange:
                {
                    var matches = item.Range.Where(t => t.IsMatch).ToList();
                    filtered.AddRange(matches);
                    break;
                }

                case ListChangeReason.Replace:
                {
                    var change   = item.Item;
                    var match    = change.Current.IsMatch;
                    var wasMatch = item.Item.Current.WasMatch;
                    if (match)
                    {
                        if (wasMatch)
                        {
                            //an update, so get the latest index and pass the index up the chain
                            var previous = filtered.Select(x => x.Item)
                                           .IndexOfOptional(change.Previous.Value.Item, ReferenceEqualityComparer <T> .Instance)
                                           .ValueOrThrow(() => new InvalidOperationException($"Cannot find index of {typeof(T).Name} -> {change.Previous.Value}. Expected to be in the list"));

                            //replace inline
                            filtered[previous.Index] = change.Current;
                        }
                        else
                        {
                            filtered.Add(change.Current);
                        }
                    }
                    else
                    {
                        if (wasMatch)
                        {
                            filtered.Remove(change.Previous.Value);
                        }
                    }
                    break;
                }

                case ListChangeReason.Refresh:
                {
                    var change   = item.Item;
                    var match    = change.Current.IsMatch;
                    var wasMatch = item.Item.Current.WasMatch;
                    if (match)
                    {
                        if (wasMatch)
                        {
                            //an update, so get the latest index and pass the index up the chain
                            var previous = filtered.Select(x => x.Item)
                                           .IndexOfOptional(change.Current.Item, ReferenceEqualityComparer <T> .Instance)
                                           .ValueOrThrow(() => new InvalidOperationException($"Cannot find index of {typeof(T).Name} -> {change.Previous.Value}. Expected to be in the list"));

                            filtered.RefreshAt(previous.Index);
                        }
                        else
                        {
                            filtered.Add(change.Current);
                        }
                    }
                    else
                    {
                        if (wasMatch)
                        {
                            filtered.Remove(change.Previous.Value);
                        }
                    }
                    break;
                }

                case ListChangeReason.Remove:
                {
                    var change = item.Item;
                    if (change.Current.IsMatch)
                    {
                        filtered.Remove(change.Current);
                    }
                    break;
                }

                case ListChangeReason.RemoveRange:
                {
                    filtered.RemoveMany(item.Range.Where(t => t.IsMatch));
                    break;
                }

                case ListChangeReason.Clear:
                {
                    filtered.ClearOrRemoveMany(item);
                    break;
                }
                }
            }
        }