-
Notifications
You must be signed in to change notification settings - Fork 6
/
SyncingCollectionViewModel.cs
353 lines (293 loc) · 19 KB
/
SyncingCollectionViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using Teronis.Collections.Algorithms;
using Teronis.Collections.Algorithms.Modifications;
using Teronis.Extensions;
using Teronis.ObjectModel.Parenting;
using Teronis.ViewModels;
namespace Teronis.Collections.Synchronization
{
/// <summary>
/// A synchronizing collection that is itself a view model.
/// The bindable collection is <see cref="SubItems"/>.
/// Remember: All childs are parents (because child extends parent), so all childs are a subset of parent / parent is a superset of child.
/// </summary>
/// <typeparam name="SubItemType"></typeparam>
/// <typeparam name="SuperItemType"></typeparam>
public abstract partial class SyncingCollectionViewModel<SubItemType, SuperItemType> : ViewModelBase, ISynchronizableCollection<SuperItemType>, INotifyCollectionModified<SubItemType, SuperItemType>, IHaveParents
where SubItemType : notnull
where SuperItemType : notnull
{
internal static CollectionModification<SubItemType, SuperItemType> ReplaceOldSuperItemsByOldSubItems(
ICollectionModification<SuperItemType, SuperItemType> superItemsSuperItemsModification,
ICollection<SubItemType> subItems)
{
var oldItems = superItemsSuperItemsModification.GetItemsBeginningFromOldIndex(subItems);
var subItemsSuperItemsModification = superItemsSuperItemsModification.CopyWithOtherItems(oldItems, superItemsSuperItemsModification.NewItems);
return subItemsSuperItemsModification;
}
internal static CollectionModification<SuperItemType, SuperItemType> CreateOldSuperItemsNewSuperItemsCollectionModification(
ICollectionModification<SubItemType, SuperItemType> oldSubItemsNewSuperItemsModification,
ICollection<SuperItemType> superItems)
{
var oldItems = oldSubItemsNewSuperItemsModification.GetItemsBeginningFromNewIndex(superItems);
var contentContentChange = oldSubItemsNewSuperItemsModification.CopyWithOtherItems(oldItems, oldSubItemsNewSuperItemsModification.NewItems);
return contentContentChange;
}
public event EventHandler? CollectionSynchronizing;
public event EventHandler? CollectionSynchronized;
public event NotifyCollectionModifiedEventHandler<SubItemType, SuperItemType>? CollectionModified;
public SubItemCollection SubItems => subItems;
public SuperItemCollection SuperItems => superItems;
public ICollectionSynchronizationMethod<SuperItemType> SynchronizationMethod { get; }
private readonly SubItemCollection subItems;
private readonly SuperItemCollection superItems;
public SyncingCollectionViewModel(ICollectionSynchronizationMethod<SuperItemType> synchronizationMethod)
{
/* Initialize collections. */
subItems = new SubItemCollection(this);
superItems = new SuperItemCollection(this);
SynchronizationMethod = synchronizationMethod ?? throw new ArgumentNullException(nameof(synchronizationMethod));
}
public SyncingCollectionViewModel() : this(CollectionSynchronizationMethod.Sequential<SuperItemType>()) { }
protected abstract SubItemType CreateSubItem(SuperItemType superItem);
/// <summary>
/// Creates for this instance a collection synchronisation mirror. The collection modifications from <paramref name="toBeImitatedCollection"/> are
/// forwarded to <see cref="ApplyCollectionModification(ICollectionModification{SuperItemType, SuperItemType}, NotifyCollectionChangedAction[])"/>
/// of this instance.
/// </summary>
/// <typeparam name="ToBeImitatedCollectionType">The generic constraint that represents the to be imitated collection.</typeparam>
/// <param name="toBeImitatedCollection">The foreign collection that is about to be imitated related to its modifications.</param>
/// <returns>A collection synchronisation mirror.</returns>
public SynchronizationMirror<ToBeImitatedCollectionType> CreateSynchronizationMirror<ToBeImitatedCollectionType>(ToBeImitatedCollectionType toBeImitatedCollection)
where ToBeImitatedCollectionType : INotifyCollectionSynchronizing<SuperItemType>, INotifyCollectionModified<SuperItemType>, INotifyCollectionSynchronized<SuperItemType> =>
new SynchronizationMirror<ToBeImitatedCollectionType>(this, toBeImitatedCollection);
protected virtual void ApplyCollectionItemAdd(in ApplyingCollectionModificationBundle modificationBundle)
{
var oldSubItemsNewSuperItems = modificationBundle.OldSubItemsNewSuperItemsModification;
var newSuperItems = oldSubItemsNewSuperItems.NewItems;
if (newSuperItems is null) {
throw new ArgumentException("No new items were given although an add collection modification action has been triggered.");
}
var newItemsCount = newSuperItems.Count;
for (int itemIndex = 0; itemIndex < newItemsCount; itemIndex++) {
var content = newSuperItems[itemIndex];
var itemInsertIndex = oldSubItemsNewSuperItems.NewIndex + itemIndex;
var item = CreateSubItem(content);
subItems.Insert(itemInsertIndex, item);
superItems.Insert(itemInsertIndex, content);
}
}
protected virtual void ApplyCollectionItemRemove(in ApplyingCollectionModificationBundle modificationBundle)
{
var contentContentChange = modificationBundle.OldSuperItemsNewSuperItemsModification;
var oldItems = contentContentChange.OldItems;
if (oldItems is null) {
throw new ArgumentException("No old items were given although a remove collection modification action has been triggered.");
}
var oldItemsCount = oldItems.Count;
var oldIndex = contentContentChange.OldIndex;
for (var oldItemIndex = oldItemsCount - 1; oldItemIndex >= 0; oldItemIndex--) {
var removeIndex = oldItemIndex + oldIndex;
//Debug.Assert(SuperItemEqualityComparer.Equals(superItems[removeIndex], oldItems[oldItemIndex]), "Removing item is not equals old item that should be removed instead");
subItems.RemoveAt(removeIndex);
superItems.RemoveAt(removeIndex);
}
}
/// <summary>
/// Does regards <see cref="ObservableCollection{T}.Move(int, int)"/>, otherwise
/// it fallbacks to <see cref="IListGenericExtensions.Move{T}(IList{T}, int, int)"/>
/// </summary>
/// <param name="modificationBundle"></param>
protected virtual void ApplyCollectionItemMove(in ApplyingCollectionModificationBundle modificationBundle)
{
var modification = modificationBundle.OldSubItemsNewSuperItemsModification;
void moveItem<T>(IList<T> list) =>
list.Move(modification.OldIndex, modification.NewIndex, modification.OldItems!.Count);
/// We need to pass the internal collection, because we don't want to
/// use the implementation of <see cref="ItemCollection{ItemType}.RemoveAt(int)"/>
/// and <see cref="ItemCollection{ItemType}.Insert(int, ItemType)"/> but
/// <see cref="List{T}.RemoveAt(int)"/> and <see cref="List{T}.Insert(int, T)"/>
/// in <see cref="IListGenericExtensions.Move{T}(IList{T}, int, int)"/>.
moveItem(subItems.Items);
moveItem(superItems.Items);
}
/// <summary>
/// Has no code inside is ready for being overriden.
/// </summary>
protected virtual void ApplyCollectionItemReplace(in ApplyingCollectionModificationBundle modificationBundle)
{ }
protected virtual void ApplyCollectionReset(in ApplyingCollectionModificationBundle modificationBundle)
{
var modification = modificationBundle.OldSubItemsNewSuperItemsModification;
var newSuperItems = modification.NewItems ?? throw new ArgumentNullException(nameof(modification.NewItems));
var newSubItems = newSuperItems.Select(x => CreateSubItem(x));
static void resetList<T>(IList<T> list, IEnumerable<T> newList)
{
list.Clear();
if (list is List<T> typedList) {
typedList.AddRange(newList);
} else {
foreach (var item in newList) {
list.Add(item);
}
}
}
resetList(subItems, newSubItems);
resetList(superItems, newSuperItems);
}
protected virtual void ApplyCollectionModificationBundle(in ApplyingCollectionModificationBundle modificationBundle, params NotifyCollectionChangedAction[] allowedActions)
{
var hasAnyAction = allowedActions is null ? true : allowedActions.Length == 0;
switch (modificationBundle.OldSuperItemsNewSuperItemsModification.Action) {
case NotifyCollectionChangedAction.Remove when hasAnyAction || (allowedActions?.Contains(NotifyCollectionChangedAction.Remove) ?? true):
ApplyCollectionItemRemove(modificationBundle);
break;
case NotifyCollectionChangedAction.Add when hasAnyAction || (allowedActions?.Contains(NotifyCollectionChangedAction.Add) ?? true):
ApplyCollectionItemAdd(modificationBundle);
break;
case NotifyCollectionChangedAction.Move when hasAnyAction || (allowedActions?.Contains(NotifyCollectionChangedAction.Move) ?? true):
ApplyCollectionItemMove(modificationBundle);
break;
case NotifyCollectionChangedAction.Replace when hasAnyAction || (allowedActions?.Contains(NotifyCollectionChangedAction.Replace) ?? true):
ApplyCollectionItemReplace(modificationBundle);
break;
case NotifyCollectionChangedAction.Reset when hasAnyAction || (allowedActions?.Contains(NotifyCollectionChangedAction.Reset) ?? true):
ApplyCollectionReset(modificationBundle);
break;
}
}
protected CollectionModificationBundle<SubItemType, SuperItemType> CreateAppliedCollectionModificationBundle(in ApplyingCollectionModificationBundle applyingModificationBundle)
{
/* We want transform new-super-items to new-sub-items because
* they have been created previously if any is existing. */
var oldSubItemsNewSuperItemsModification = applyingModificationBundle.OldSubItemsNewSuperItemsModification;
var newSubItems = oldSubItemsNewSuperItemsModification.GetItemsBeginningFromNewIndex(subItems);
var oldSubItemsNewSubItemsModification = oldSubItemsNewSuperItemsModification.CopyWithOtherItems(oldSubItemsNewSuperItemsModification.OldItems, newSubItems);
var appliedModificationBundle = new CollectionModificationBundle<SubItemType, SuperItemType>(
oldSubItemsNewSubItemsModification,
applyingModificationBundle.OldSubItemsNewSuperItemsModification,
applyingModificationBundle.OldSuperItemsNewSuperItemsModification);
return appliedModificationBundle;
}
protected void OnCollectionModified(CollectionModifiedEventArgs<SubItemType, SuperItemType> args)
=> CollectionModified?.Invoke(this, args);
protected virtual void ApplyCollectionModification(ICollectionModification<SuperItemType, SuperItemType> superItemsModification, params NotifyCollectionChangedAction[] allowedActions)
{
var oldSubItemsNewSuperItemsModification = ReplaceOldSuperItemsByOldSubItems(superItemsModification, subItems);
var applyingModificationBundle = new ApplyingCollectionModificationBundle(oldSubItemsNewSuperItemsModification, superItemsModification);
ApplyCollectionModificationBundle(applyingModificationBundle, allowedActions);
var appliedModificationBundle = CreateAppliedCollectionModificationBundle(applyingModificationBundle);
var args = new CollectionModifiedEventArgs<SubItemType, SuperItemType>(appliedModificationBundle);
OnCollectionModified(args);
}
protected void OnCollectionSynchronizing() =>
CollectionSynchronizing?.Invoke(this, new EventArgs());
protected void OnCollectionSynchronized() =>
CollectionSynchronized?.Invoke(this, new EventArgs());
/// <summary>
/// Synchronizes collection with <paramref name="items"/>.
/// </summary>
/// <param name="items"></param>
public virtual void SynchronizeCollection(IEnumerable<SuperItemType>? items)
{
OnCollectionSynchronizing();
foreach (var modification in SynchronizationMethod.YieldCollectionModifications(superItems, items)) {
ApplyCollectionModification(modification);
}
OnCollectionSynchronized();
}
///// <summary>
///// Updates existing items with <paramref name="items"/>.
///// <br/>Depending on <see cref="ItemReplaceStrategy"/> and
///// <see cref="ApplyCollectionItemReplace(in ApplyingCollectionModificationBundle)"/>
///// this can mean for example to replace super items or update sub items.
///// </summary>
///// <param name="items">The items used to find updatable items.</param>
//public virtual void UpdateItems(IEnumerable<SuperItemType> items)
//{
// OnCollectionSynchronizing();
// items ??= Enumerable.Empty<SuperItemType>();
// var modifications = EqualityTrailingCollectionModifications.YieldCollectionModifications(superItems, items, SuperItemEqualityComparer);
// foreach (var tuple in IEnumerableICollectionModificationUtils.YieldTuplesButOnlyReplaceModificationWithInitialOldIndex(modifications)) {
// if (tuple.Modification.OldItems is null) {
// throw new InvalidOperationException("The old items were null.");
// }
// var initialOldIndex = tuple.InitialOldIndex;
// var oldItems = SuperItems.Skip(initialOldIndex).Take(tuple.Modification.OldItems.Count).ToList();
// var newModification = tuple.Modification.CopyWithOtherValues(
// oldItems: oldItems,
// oldIndex: initialOldIndex,
// newIndex: initialOldIndex);
// ApplyCollectionModification(newModification, NotifyCollectionChangedAction.Replace);
// }
// OnCollectionSynchronized();
//}
///// <summary>
///// Inserts not existing items from <paramref name="items"/> and updates existing items
///// with <paramref name="items"/>.
///// <br/>Depending on <see cref="ItemReplaceStrategy"/> and/or
///// <see cref="ApplyCollectionItemReplace(in ApplyingCollectionModificationBundle)"/>
///// this can mean for example to replace super items or update sub items.
///// </summary>
///// <br/>The items that are not added or updated will be at the end of the list.
///// <param name="items">The items used to find addable and updatable items.</param>
//public virtual void InsertAndUpdateItems(IEnumerable<SuperItemType> items)
//{
// OnCollectionSynchronizing();
// items ??= Enumerable.Empty<SuperItemType>();
// var modifications = superItems.GetCollectionModifications(items, SuperItemEqualityComparer);
// foreach (var modification in modifications) {
// // This is algorithm dependent. The remove modification are coming at last.
// if (modification.Action == NotifyCollectionChangedAction.Remove) {
// break;
// }
// ApplyCollectionModification(modification);
// }
// OnCollectionSynchronized();
//}
//public void InsertAndUpdateItems<ItemType, KeyType>(IReadOnlyList<SuperItemType> items, KeyedItemIndexTracker<ItemType, KeyType> tracker,
// Func<SuperItemType, KeyType> getItemKey, IComparer<KeyType> comparer)
//{
// OnCollectionSynchronizing();
// items ??= new List<SuperItemType>(0);
// var modifications = EqualityTrailingCollectionModifications.YieldCollectionModifications(superItems, items, SuperItemEqualityComparer);
// if (!(ReferenceEquals(tracker.ItemCollection, SubItems) || ReferenceEquals(tracker.ItemCollection, SuperItems))) {
// throw new ArgumentException("Tracker is not originating from this synchronizing collection.");
// }
// var sortedDictionary = new SortedDictionary<KeyType, SuperItemType>(comparer);
// foreach (var pair in tracker.KeyedItemIndexes) {
// sortedDictionary.Add(pair.Key, SuperItems[pair.Value]);
// }
// var itemsCount = items.Count;
// for (var index = 0; index < itemsCount; index++) {
// var itemKey = getItemKey(items[index]);
// sortedDictionary.Add(itemKey, items[index]);
// }
// SynchronizeCollection(sortedDictionary.Values);
//}
//public void InsertAndUpdateItems<ItemType, KeyType>(IReadOnlyList<SuperItemType> items, KeyedItemIndexTracker<ItemType,KeyType> tracker,
// Func<SuperItemType, KeyType> getItemKey) =>
// InsertAndUpdateItems(items, tracker, getItemKey, Comparer<KeyType>.Default);
//public void InsertAndUpdateItems<KeyType>(IReadOnlyList<SuperItemType> items, KeyedItemIndexTracker<SuperItemType, KeyType> tracker,
// IComparer<KeyType> comparer) =>
// InsertAndUpdateItems(items, tracker, tracker.GetItemKeyDelegate, comparer);
//public void InsertAndUpdateItems<KeyType>(IReadOnlyList<SuperItemType> items, KeyedItemIndexTracker<SuperItemType, KeyType> tracker) =>
// InsertAndUpdateItems(items, tracker, tracker.GetItemKeyDelegate, Comparer<KeyType>.Default);
protected readonly struct ApplyingCollectionModificationBundle
{
public ICollectionModification<SubItemType, SuperItemType> OldSubItemsNewSuperItemsModification { get; }
public ICollectionModification<SuperItemType, SuperItemType> OldSuperItemsNewSuperItemsModification { get; }
public ApplyingCollectionModificationBundle(ICollectionModification<SubItemType, SuperItemType> oldSubItemsNewSuperItemsModification,
ICollectionModification<SuperItemType, SuperItemType> oldSuperItemsNewSuperItemsModification)
{
OldSubItemsNewSuperItemsModification = oldSubItemsNewSuperItemsModification;
OldSuperItemsNewSuperItemsModification = oldSuperItemsNewSuperItemsModification;
}
}
}
}