Index: Core/Common/src/Core.Common.Base/RecursiveObserver.cs
===================================================================
diff -u -r07b0e83704b56684d617cd6e8b8570417654912a -rf65694c90375dbbed8fea4db8e7d0873cc659669
--- Core/Common/src/Core.Common.Base/RecursiveObserver.cs (.../RecursiveObserver.cs) (revision 07b0e83704b56684d617cd6e8b8570417654912a)
+++ Core/Common/src/Core.Common.Base/RecursiveObserver.cs (.../RecursiveObserver.cs) (revision f65694c90375dbbed8fea4db8e7d0873cc659669)
@@ -25,95 +25,115 @@
namespace Core.Common.Base
{
///
- /// Class that implements in a way that a hierarchy of objects can be observed recursively.
+ /// Class that implements in a way that a hierarchy of objects can be observed.
///
///
- /// The root being observed by instances of this class can be dynamically changed.
+ /// The root container () being observed by instances of this class can be dynamically changed.
///
- /// The type of objects to observe recursively.
- public class RecursiveObserver : IObserver, IDisposable where T : class, IObservable
+ /// The type of the item containers that specify the object hierarchy.
+ /// The type of items (in the containers) that should be observed.
+ public class RecursiveObserver : IObserver, IDisposable
+ where TContainer : class, IObservable
+ where TObservable : class, IObservable
{
- private T rootObservable;
+ private TContainer rootContainer;
private readonly Action updateObserverAction;
- private readonly Func> getChildObservables;
- private readonly IList observedObjects = new List();
+ private readonly Func> getChildren;
+ private readonly IList observedContainers = new List();
+ private readonly IList observedChildren = new List();
+ private readonly Observer containerObserver;
///
- /// Creates a new instance of the class.
+ /// Creates a new instance of the class.
///
- /// The action to perform on notifications coming from one of the items of the hierarchy of observed objects.
- /// The method used for recursively obtaining the objects to observe.
- public RecursiveObserver(Action updateObserverAction, Func> getChildObservables)
+ /// The action to perform on notifications coming from one of the items of the hierarchy.
+ /// The method used for recursively obtaining the children of objects in the hierarchy.
+ public RecursiveObserver(Action updateObserverAction, Func> getChildren)
{
this.updateObserverAction = updateObserverAction;
- this.getChildObservables = getChildObservables;
+ this.getChildren = getChildren;
+
+ // Ensure subscriptions are updated (detach/attach) on changes in the hierarchy
+ containerObserver = new Observer(UpdateObservedObjects);
}
///
- /// Gets or sets the root object to observe.
+ /// Gets or sets the root container.
///
- public T Observable
+ public TContainer Observable
{
get
{
- return rootObservable;
+ return rootContainer;
}
set
{
- rootObservable = value;
+ rootContainer = value;
UpdateObservedObjects();
}
}
+ public void Dispose()
+ {
+ Observable = null;
+ }
+
public void UpdateObserver()
{
updateObserverAction();
-
- // Update the list of observed objects as observables might have been added/removed
- UpdateObservedObjects();
}
private void UpdateObservedObjects()
{
- // Detach from the currently observed objects
- foreach (var observedObject in observedObjects)
+ // Detach from the currently observed containers
+ foreach (var observedObject in observedContainers)
{
+ observedObject.Detach(containerObserver);
+ }
+
+ // Detach from the currently observed children
+ foreach (var observedObject in observedChildren)
+ {
observedObject.Detach(this);
}
- // Clear the list of observed objects
- observedObjects.Clear();
+ // Clear the lists of observed objects
+ observedContainers.Clear();
+ observedChildren.Clear();
// If relevant, start observing objects again
- if (rootObservable != null)
+ if (rootContainer != null)
{
- foreach (var objectToObserve in GetObservablesRecursive(rootObservable))
- {
- objectToObserve.Attach(this);
- observedObjects.Add(objectToObserve);
- }
+ ObserveObjectsRecursively(rootContainer);
}
}
- private IEnumerable GetObservablesRecursive(T observable)
+ private void ObserveObjectsRecursively(TContainer container)
{
- var observables = new List
- {
- observable
- };
+ container.Attach(containerObserver);
+ observedContainers.Add(container);
- foreach (var childObservable in getChildObservables(observable))
+ var observable = container as TObservable;
+ if (observable != null)
{
- observables.AddRange(GetObservablesRecursive(childObservable));
+ observable.Attach(this);
+ observedChildren.Add(observable);
}
- return observables;
+ foreach (var child in getChildren(container))
+ {
+ if (child is TContainer)
+ {
+ ObserveObjectsRecursively((TContainer) child);
+ }
+ else if (child is TObservable)
+ {
+ observable = (TObservable) child;
+ observable.Attach(this);
+ observedChildren.Add(observable);
+ }
+ }
}
-
- public void Dispose()
- {
- Observable = null;
- }
}
}
Index: Core/Common/test/Core.Common.Base.Test/RecursiveObserverTest.cs
===================================================================
diff -u -rb04b130df2b11fd54962e4a0b9be92265e580831 -rf65694c90375dbbed8fea4db8e7d0873cc659669
--- Core/Common/test/Core.Common.Base.Test/RecursiveObserverTest.cs (.../RecursiveObserverTest.cs) (revision b04b130df2b11fd54962e4a0b9be92265e580831)
+++ Core/Common/test/Core.Common.Base.Test/RecursiveObserverTest.cs (.../RecursiveObserverTest.cs) (revision f65694c90375dbbed8fea4db8e7d0873cc659669)
@@ -34,7 +34,7 @@
var counter = 0;
// Call
- var recursiveObserver = new RecursiveObserver(() => { counter++; }, GetChildObservables);
+ var recursiveObserver = new RecursiveObserver(() => { counter++; }, GetChildren);
// Assert
Assert.IsInstanceOf(recursiveObserver);
@@ -49,159 +49,260 @@
[TestCase(1)]
[TestCase(2)]
[TestCase(100)]
- public void RecursiveObserver_WithObservableHierarchy_NotifyObserversAtSpecifiedLevelResultsInPerformingUpdateObserversAction(int nestingLevel)
+ public void RecursiveObserverObservingContainers_NotifyObserversAtSpecifiedLevel_UpdateObserversActionShouldBePerformed(int nestingLevel)
{
- // Setup
+ // Given
var counter = 0;
- var rootObservable = new TestObservable();
+ var rootContainer = new TestContainer();
+ var currentNestedContainer = rootContainer;
+ var currentTestObservable = new TestObservable();
- var currentNestedObservable = rootObservable;
+ InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable);
- for (var i = 0; i < nestingLevel; i++)
+ var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren)
{
- var newObservable = new TestObservable();
+ Observable = rootContainer
+ };
- currentNestedObservable.ChildTestObservables.Add(newObservable);
+ // When
+ currentNestedContainer.NotifyObservers();
- currentNestedObservable = newObservable;
- }
+ // Then
+ Assert.AreEqual(1, counter);
- var recursiveObserver = new RecursiveObserver(() => counter++, GetChildObservables)
+ currentTestObservable.NotifyObservers();
+ Assert.AreEqual(1, counter); // Nothing should have happened
+ }
+
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(100)]
+ public void RecursiveObserverObservingItemsInContainers_NotifyObserversAtSpecifiedLevel_UpdateObserversActionShouldBePerformed(int nestingLevel)
+ {
+ // Given
+ var counter = 0;
+ var rootContainer = new TestContainer();
+ var currentNestedContainer = rootContainer;
+ var currentTestObservable = new TestObservable();
+
+ InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable);
+
+ var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren)
{
- Observable = rootObservable
+ Observable = rootContainer
};
- // Call
- currentNestedObservable.NotifyObservers();
+ // When
+ currentTestObservable.NotifyObservers();
- // Assert
+ // Then
Assert.AreEqual(1, counter);
+
+ currentNestedContainer.NotifyObservers();
+ Assert.AreEqual(1, counter); // Nothing should have happened
}
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
[TestCase(100)]
- public void RecursiveObserver_WithObservableHierarchySetAndThenUnset_NotifyObserversNoLongerResultsInPerformingUpdateObserversAction(int nestingLevel)
+ public void RecursiveObserverObservingContainers_RootContainerSetAndThenUnset_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel)
{
- // Setup
+ // Given
var counter = 0;
- var rootObservable = new TestObservable();
+ var rootContainer = new TestContainer();
+ var currentNestedContainer = rootContainer;
+ var currentTestObservable = new TestObservable();
- var currentNestedObservable = rootObservable;
+ InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable);
- for (var i = 0; i < nestingLevel; i++)
+ var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren)
{
- var newObservable = new TestObservable();
+ Observable = rootContainer
+ };
- currentNestedObservable.ChildTestObservables.Add(newObservable);
+ // When
+ recursiveObserver.Observable = null;
+ currentNestedContainer.NotifyObservers();
- currentNestedObservable = newObservable;
- }
+ // Then
+ Assert.AreEqual(0, counter);
+ }
- var recursiveObserver = new RecursiveObserver(() => counter++, GetChildObservables)
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(100)]
+ public void RecursiveObserverObservingItemsInContainers_RootContainerSetAndThenUnset_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel)
+ {
+ // Given
+ var counter = 0;
+ var rootContainer = new TestContainer();
+ var currentNestedContainer = rootContainer;
+ var currentTestObservable = new TestObservable();
+
+ InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable);
+
+ var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren)
{
- Observable = rootObservable
+ Observable = rootContainer
};
+ // When
recursiveObserver.Observable = null;
+ currentTestObservable.NotifyObservers();
- // Call
- currentNestedObservable.NotifyObservers();
-
- // Assert
+ // Then
Assert.AreEqual(0, counter);
}
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
[TestCase(100)]
- public void RecursiveObserver_WithObservableHierarchySetAndThenDisposed_NotifyObserversNoLongerResultsInPerformingUpdateObserversAction(int nestingLevel)
+ public void RecursiveObserverObservingContainers_RecursiveObserverDispose_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel)
{
- // Setup
+ // Given
var counter = 0;
- var rootObservable = new TestObservable();
+ var rootContainer = new TestContainer();
+ var currentNestedContainer = rootContainer;
+ var currentTestObservable = new TestObservable();
- var currentNestedObservable = rootObservable;
+ InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable);
- for (var i = 0; i < nestingLevel; i++)
+ var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren)
{
- var newObservable = new TestObservable();
+ Observable = rootContainer
+ };
- currentNestedObservable.ChildTestObservables.Add(newObservable);
+ // When
+ recursiveObserver.Dispose();
+ currentNestedContainer.NotifyObservers();
- currentNestedObservable = newObservable;
- }
+ // Then
+ Assert.AreEqual(0, counter);
+ Assert.IsNull(recursiveObserver.Observable);
+ }
- var recursiveObserver = new RecursiveObserver(() => counter++, GetChildObservables)
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(100)]
+ public void RecursiveObserverObservingItemsInContainers_RecursiveObserverDispose_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel)
+ {
+ // Given
+ var counter = 0;
+ var rootContainer = new TestContainer();
+ var currentNestedContainer = rootContainer;
+ var currentTestObservable = new TestObservable();
+
+ InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable);
+
+ var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren)
{
- Observable = rootObservable
+ Observable = rootContainer
};
+ // When
recursiveObserver.Dispose();
+ currentTestObservable.NotifyObservers();
- // Call
- currentNestedObservable.NotifyObservers();
-
- // Assert
+ // Then
Assert.AreEqual(0, counter);
+ Assert.IsNull(recursiveObserver.Observable);
}
[TestCase(1)]
[TestCase(2)]
[TestCase(100)]
- public void RecursiveObserver_WithObservableHierarchySetAndThenObservedItemRemoved_NotifyObserversForRemovedItemNoLongerResultsInPerformingUpdateObserversAction(int nestingLevel)
+ public void RecursiveObserverObservingContainers_ContainerItemsRemoved_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel)
{
- // Setup
+ // Given
var counter = 0;
- var rootObservable = new TestObservable();
+ var rootContainer = new TestContainer();
+ var currentNestedContainer = rootContainer;
+ var currentTestObservable = new TestObservable();
- var previousNestedObservable = new TestObservable();
- var currentNestedObservable = rootObservable;
+ InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable);
- for (var i = 0; i < nestingLevel; i++)
+ var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren)
{
- var newObservable = new TestObservable();
+ Observable = rootContainer
+ };
- currentNestedObservable.ChildTestObservables.Add(newObservable);
+ // When
+ rootContainer.Children.Clear();
+ rootContainer.NotifyObservers(); // Collection changes should always be notified
- previousNestedObservable = currentNestedObservable;
- currentNestedObservable = newObservable;
- }
+ // Precondition (counter equals 1 due to previous notification)
+ Assert.AreEqual(1, counter);
- var recursiveObserver = new RecursiveObserver(() => counter++, GetChildObservables)
+ currentNestedContainer.NotifyObservers();
+
+ // Then
+ Assert.AreEqual(1, counter);
+ }
+
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(100)]
+ public void RecursiveObserverObservingItemsInContainers_ContainerItemsRemoved_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel)
+ {
+ // Given
+ var counter = 0;
+ var rootContainer = new TestContainer();
+ var currentNestedContainer = rootContainer;
+ var currentTestObservable = new TestObservable();
+
+ InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable);
+
+ var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren)
{
- Observable = rootObservable
+ Observable = rootContainer
};
- previousNestedObservable.ChildTestObservables.Clear();
- previousNestedObservable.NotifyObservers();
- counter = 0;
+ // When
+ rootContainer.Children.Clear();
+ rootContainer.NotifyObservers(); // Collection changes should always be notified
- // Call
- currentNestedObservable.NotifyObservers();
+ currentTestObservable.NotifyObservers();
- // Assert
+ // Then
Assert.AreEqual(0, counter);
}
- private class TestObservable : Observable
+ private static void InitializeHierarchy(int nestingLevel, ref TestContainer currentNestedContainer, ref TestObservable currentTestObservable)
{
- private readonly IList childTestObservables = new List();
+ for (var i = 0; i < nestingLevel; i++)
+ {
+ var newNestedContainer = new TestContainer();
+ var newTestObservable = new TestObservable();
- public IList ChildTestObservables
+ currentNestedContainer.Children.Add(new object());
+ currentNestedContainer.Children.Add(newTestObservable);
+ currentNestedContainer.Children.Add(newNestedContainer);
+
+ currentTestObservable = newTestObservable;
+ currentNestedContainer = newNestedContainer;
+ }
+ }
+
+ private class TestContainer : Observable
+ {
+ private readonly IList