diff --git a/.build/dependencies.props b/.build/dependencies.props
index f97e58774e..cac54299e2 100644
--- a/.build/dependencies.props
+++ b/.build/dependencies.props
@@ -69,7 +69,6 @@
3.17.0
3.13.1
1.9.1.1
- 7.2.0.1422
2.7.8
1.1.0
0.4.1.1
diff --git a/.rat-excludes b/.rat-excludes
index 257b685363..cddb9ba01b 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -27,6 +27,7 @@ psake/*
KStemData\d+\.cs
Snowball/*
Egothor.Stemmer/*
+Events/*
Sax/*
JaspellTernarySearchTrie\.cs
RectangularArrays\.cs
diff --git a/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs b/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
index 3d58fadc54..19f85a64d6 100644
--- a/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
+++ b/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
@@ -3,7 +3,7 @@
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Prism.Events;
+using Lucene.Net.Util.Events;
#endif
using System;
using System.Collections.Generic;
@@ -103,7 +103,7 @@ private CachedOrds GetCachedOrds(AtomicReaderContext context)
ordsCache.Add(cacheKey, ords);
#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
// LUCENENET specific: Add weak event handler for .NET Standard 2.0 and .NET Framework, since we don't have an enumerator to use
- context.Reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent());
+ context.Reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent());
#endif
}
return ords;
@@ -229,8 +229,8 @@ public virtual long RamBytesUsed()
// we use a weak event to retrieve the CachedOrds instances. We look each of these up here to avoid the need
// to attach events to the CachedOrds instances themselves (thus using the existing IndexReader.Dispose()
// method to detach the events rather than using a finalizer in CachedOrds to ensure they are cleaned up).
- var e = new Events.GetCacheKeysEventArgs();
- eventAggregator.GetEvent().Publish(e);
+ var e = new WeakEvents.GetCacheKeysEventArgs();
+ eventAggregator.GetEvent().Publish(e);
foreach (var key in e.CacheKeys)
if (ordsCache.TryGetValue(key, out CachedOrds value))
cachedOrdsList.Add(value);
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/MockDelegateReference.cs b/src/Lucene.Net.Tests/Support/Util/Events/MockDelegateReference.cs
new file mode 100644
index 0000000000..7565e2f544
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/MockDelegateReference.cs
@@ -0,0 +1,43 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+
+namespace Lucene.Net.Util.Events
+{
+ class MockDelegateReference : IDelegateReference
+ {
+ public Delegate Target { get; set; }
+
+ public MockDelegateReference()
+ {
+
+ }
+
+ public MockDelegateReference(Delegate target)
+ {
+ Target = target;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestBackgroundEventSubscription.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestBackgroundEventSubscription.cs
new file mode 100644
index 0000000000..d959c297ad
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestBackgroundEventSubscription.cs
@@ -0,0 +1,91 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Threading;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+ [TestFixture]
+ public class TestBackgroundEventSubscription
+ {
+ [Test]
+ public void ShouldReceiveDelegateOnDifferentThread()
+ {
+ ManualResetEvent completeEvent = new ManualResetEvent(false);
+ SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+ SynchronizationContext calledSyncContext = null;
+ Action action = delegate
+ {
+ calledSyncContext = SynchronizationContext.Current;
+ completeEvent.Set();
+ };
+
+ IDelegateReference actionDelegateReference = new MockDelegateReference() { Target = action };
+ IDelegateReference filterDelegateReference = new MockDelegateReference() { Target = (Predicate)delegate { return true; } };
+
+ var eventSubscription = new BackgroundEventSubscription(actionDelegateReference, filterDelegateReference);
+
+
+ var publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.NotNull(publishAction);
+
+ publishAction.Invoke(null);
+
+ completeEvent.WaitOne(5000);
+
+ Assert.AreNotEqual(SynchronizationContext.Current, calledSyncContext);
+ }
+
+ [Test]
+ public void ShouldReceiveDelegateOnDifferentThreadNonGeneric()
+ {
+ var completeEvent = new ManualResetEvent(false);
+ SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+ SynchronizationContext calledSyncContext = null;
+ Action action = delegate
+ {
+ calledSyncContext = SynchronizationContext.Current;
+ completeEvent.Set();
+ };
+
+ IDelegateReference actionDelegateReference = new MockDelegateReference() { Target = action };
+
+ var eventSubscription = new BackgroundEventSubscription(actionDelegateReference);
+
+ var publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.NotNull(publishAction);
+
+ publishAction.Invoke(null);
+
+ completeEvent.WaitOne(5000);
+
+ Assert.AreNotEqual(SynchronizationContext.Current, calledSyncContext);
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestDelegateReference.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestDelegateReference.cs
new file mode 100644
index 0000000000..2d6ed5bfe5
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestDelegateReference.cs
@@ -0,0 +1,214 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Threading.Tasks;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+ [TestFixture]
+ public class TestDelegateReference
+ {
+ [Test]
+ public void KeepAlivePreventsDelegateFromBeingCollected()
+ {
+ var delegates = new SomeClassHandler();
+ var delegateReference = new DelegateReference((Action)delegates.DoEvent, true);
+
+ delegates = null;
+ GC.Collect();
+
+ Assert.NotNull(delegateReference.Target);
+ }
+
+ [Test]
+ public async Task NotKeepAliveAllowsDelegateToBeCollected()
+ {
+ var delegates = new SomeClassHandler();
+ var delegateReference = new DelegateReference((Action)delegates.DoEvent, false);
+
+ delegates = null;
+ await Task.Delay(100);
+ GC.Collect();
+
+ Assert.Null(delegateReference.Target);
+ }
+
+ [Test]
+ public async Task NotKeepAliveKeepsDelegateIfStillAlive()
+ {
+ var delegates = new SomeClassHandler();
+ var delegateReference = new DelegateReference((Action)delegates.DoEvent, false);
+
+ GC.Collect();
+
+ Assert.NotNull(delegateReference.Target);
+
+ GC.KeepAlive(delegates); //Makes delegates ineligible for garbage collection until this point (to prevent oompiler optimizations that may release the referenced object prematurely).
+ delegates = null;
+ await Task.Delay(100);
+ GC.Collect();
+
+ Assert.Null(delegateReference.Target);
+ }
+
+ [Test]
+ public void TargetShouldReturnAction()
+ {
+ var classHandler = new SomeClassHandler();
+ Action myAction = new Action(classHandler.MyAction);
+
+ var weakAction = new DelegateReference(myAction, false);
+
+ ((Action)weakAction.Target)("payload");
+ Assert.AreEqual("payload", classHandler.MyActionArg);
+ }
+
+ [Test]
+ public async Task ShouldAllowCollectionOfOriginalDelegate()
+ {
+ var classHandler = new SomeClassHandler();
+ Action myAction = new Action(classHandler.MyAction);
+
+ var weakAction = new DelegateReference(myAction, false);
+
+ var originalAction = new WeakReference(myAction);
+ myAction = null;
+ await Task.Delay(100);
+ GC.Collect();
+ Assert.False(originalAction.IsAlive);
+
+ ((Action)weakAction.Target)("payload");
+ Assert.AreEqual("payload", classHandler.MyActionArg);
+ }
+
+ [Test]
+ public async Task ShouldReturnNullIfTargetNotAlive()
+ {
+ SomeClassHandler handler = new SomeClassHandler();
+ var weakHandlerRef = new WeakReference(handler);
+
+ var action = new DelegateReference((Action)handler.DoEvent, false);
+
+ handler = null;
+ await Task.Delay(100);
+ GC.Collect();
+ Assert.False(weakHandlerRef.IsAlive);
+
+ Assert.Null(action.Target);
+ }
+
+ [Test]
+ public void WeakDelegateWorksWithStaticMethodDelegates()
+ {
+ var action = new DelegateReference((Action)SomeClassHandler.StaticMethod, false);
+
+ Assert.NotNull(action.Target);
+ }
+
+ [Test]
+ public void TargetEqualsActionShouldReturnTrue()
+ {
+ var classHandler = new SomeClassHandler();
+ Action myAction = new Action(classHandler.MyAction);
+
+ var weakAction = new DelegateReference(myAction, false);
+
+ Assert.True(weakAction.TargetEquals(new Action(classHandler.MyAction)));
+ }
+
+ [Test]
+ public async Task TargetEqualsNullShouldReturnTrueIfTargetNotAlive()
+ {
+ SomeClassHandler handler = new SomeClassHandler();
+ var weakHandlerRef = new WeakReference(handler);
+
+ var action = new DelegateReference((Action)handler.DoEvent, false);
+
+ handler = null;
+
+ // Intentional delay to encourage Garbage Collection to actually occur
+ await Task.Delay(100);
+ GC.Collect();
+ Assert.False(weakHandlerRef.IsAlive);
+
+ Assert.True(action.TargetEquals(null));
+ }
+
+ [Test]
+ public void TargetEqualsNullShouldReturnFalseIfTargetAlive()
+ {
+ SomeClassHandler handler = new SomeClassHandler();
+ var weakHandlerRef = new WeakReference(handler);
+
+ var action = new DelegateReference((Action)handler.DoEvent, false);
+
+ Assert.False(action.TargetEquals(null));
+ Assert.True(weakHandlerRef.IsAlive);
+ GC.KeepAlive(handler);
+ }
+
+ [Test]
+ public void TargetEqualsWorksWithStaticMethodDelegates()
+ {
+ var action = new DelegateReference((Action)SomeClassHandler.StaticMethod, false);
+
+ Assert.True(action.TargetEquals((Action)SomeClassHandler.StaticMethod));
+ }
+
+ //todo: fix
+ //[Test]
+ //public void NullDelegateThrows()
+ //{
+ // Assert.ThrowsException(() =>
+ // {
+ // var action = new DelegateReference(null, true);
+ // });
+ //}
+
+ public class SomeClassHandler
+ {
+ public string MyActionArg;
+
+ public void DoEvent(string value)
+ {
+ string myValue = value;
+ }
+
+ public static void StaticMethod()
+ {
+#pragma warning disable 0219
+ int i = 0;
+#pragma warning restore 0219
+ }
+
+ public void MyAction(string arg)
+ {
+ MyActionArg = arg;
+ }
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestDispatcherEventSubscription.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestDispatcherEventSubscription.cs
new file mode 100644
index 0000000000..51eed98a84
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestDispatcherEventSubscription.cs
@@ -0,0 +1,123 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Threading;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+ [TestFixture]
+ public class TestDispatcherEventSubscription
+ {
+ [Test]
+ public void ShouldCallInvokeOnDispatcher()
+ {
+ DispatcherEventSubscription eventSubscription = null;
+
+ IDelegateReference actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action)(arg =>
+ {
+ return;
+ })
+ };
+
+ IDelegateReference filterDelegateReference = new MockDelegateReference
+ {
+ Target = (Predicate)(arg => true)
+ };
+ var mockSyncContext = new MockSynchronizationContext();
+
+ eventSubscription = new DispatcherEventSubscription(actionDelegateReference, filterDelegateReference, mockSyncContext);
+
+ eventSubscription.GetExecutionStrategy().Invoke(new object[0]);
+
+ Assert.True(mockSyncContext.InvokeCalled);
+ }
+
+ [Test]
+ public void ShouldCallInvokeOnDispatcherNonGeneric()
+ {
+ DispatcherEventSubscription eventSubscription = null;
+
+ IDelegateReference actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action)(() =>
+ { })
+ };
+
+ var mockSyncContext = new MockSynchronizationContext();
+
+ eventSubscription = new DispatcherEventSubscription(actionDelegateReference, mockSyncContext);
+
+ eventSubscription.GetExecutionStrategy().Invoke(new object[0]);
+
+ Assert.True(mockSyncContext.InvokeCalled);
+ }
+
+ [Test]
+ public void ShouldPassParametersCorrectly()
+ {
+ IDelegateReference actionDelegateReference = new MockDelegateReference()
+ {
+ Target =
+ (Action)(arg1 =>
+ {
+ return;
+ })
+ };
+ IDelegateReference filterDelegateReference = new MockDelegateReference
+ {
+ Target = (Predicate)(arg => true)
+ };
+
+ var mockSyncContext = new MockSynchronizationContext();
+
+ DispatcherEventSubscription eventSubscription = new DispatcherEventSubscription(actionDelegateReference, filterDelegateReference, mockSyncContext);
+
+ var executionStrategy = eventSubscription.GetExecutionStrategy();
+ Assert.NotNull(executionStrategy);
+
+ object argument1 = new object();
+
+ executionStrategy.Invoke(new[] { argument1 });
+
+ Assert.AreSame(argument1, mockSyncContext.InvokeArg);
+ }
+ }
+
+ internal class MockSynchronizationContext : SynchronizationContext
+ {
+ public bool InvokeCalled;
+ public object InvokeArg;
+
+ public override void Post(SendOrPostCallback d, object state)
+ {
+ InvokeCalled = true;
+ InvokeArg = state;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestEventAggregator.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestEventAggregator.cs
new file mode 100644
index 0000000000..5f945013f0
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestEventAggregator.cs
@@ -0,0 +1,45 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+ [TestFixture]
+ public class TestEventAggregator
+ {
+ [Test]
+ public void GetReturnsSingleInstancesOfSameEventType()
+ {
+ var eventAggregator = new EventAggregator();
+ var instance1 = eventAggregator.GetEvent();
+ var instance2 = eventAggregator.GetEvent();
+
+ Assert.AreSame(instance2, instance1);
+ }
+
+ internal class MockEventBase : EventBase { }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestEventBase.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestEventBase.cs
new file mode 100644
index 0000000000..c71ddcb3ac
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestEventBase.cs
@@ -0,0 +1,138 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+ [TestFixture]
+ public class TestEventBase
+ {
+ [Test]
+ public void CanPublishSimpleEvents()
+ {
+ var eventBase = new TestableEventBase();
+ var eventSubscription = new MockEventSubscription();
+ bool eventPublished = false;
+ eventSubscription.GetPublishActionReturnValue = delegate
+ {
+ eventPublished = true;
+ };
+ eventBase.Subscribe(eventSubscription);
+
+ eventBase.Publish();
+
+ Assert.True(eventSubscription.GetPublishActionCalled);
+ Assert.True(eventPublished);
+ }
+
+ [Test]
+ public void CanHaveMultipleSubscribersAndRaiseCustomEvent()
+ {
+ var customEvent = new TestableEventBase();
+ Payload payload = new Payload();
+ object[] received1 = null;
+ object[] received2 = null;
+ var eventSubscription1 = new MockEventSubscription();
+ eventSubscription1.GetPublishActionReturnValue = delegate (object[] args) { received1 = args; };
+ var eventSubscription2 = new MockEventSubscription();
+ eventSubscription2.GetPublishActionReturnValue = delegate (object[] args) { received2 = args; };
+
+ customEvent.Subscribe(eventSubscription1);
+ customEvent.Subscribe(eventSubscription2);
+
+ customEvent.Publish(payload);
+
+ Assert.AreEqual(1, received1.Length);
+ Assert.AreSame(received1[0], payload);
+
+ Assert.AreEqual(1, received2.Length);
+ Assert.AreSame(received2[0], payload);
+ }
+
+ [Test]
+ public void ShouldSubscribeAndUnsubscribe()
+ {
+ var eventBase = new TestableEventBase();
+
+ var eventSubscription = new MockEventSubscription();
+ eventBase.Subscribe(eventSubscription);
+
+ Assert.NotNull(eventSubscription.SubscriptionToken);
+ Assert.True(eventBase.Contains(eventSubscription.SubscriptionToken));
+
+ eventBase.Unsubscribe(eventSubscription.SubscriptionToken);
+
+ Assert.False(eventBase.Contains(eventSubscription.SubscriptionToken));
+ }
+
+ [Test]
+ public void WhenEventSubscriptionActionIsNullPruneItFromList()
+ {
+ var eventBase = new TestableEventBase();
+
+ var eventSubscription = new MockEventSubscription();
+ eventSubscription.GetPublishActionReturnValue = null;
+
+ var token = eventBase.Subscribe(eventSubscription);
+
+ eventBase.Publish();
+
+ Assert.False(eventBase.Contains(token));
+ }
+
+
+ class TestableEventBase : EventBase
+ {
+ public SubscriptionToken Subscribe(IEventSubscription subscription)
+ {
+ return base.InternalSubscribe(subscription);
+ }
+
+ public void Publish(params object[] arguments)
+ {
+ base.InternalPublish(arguments);
+ }
+ }
+
+ class MockEventSubscription : IEventSubscription
+ {
+ public Action GetPublishActionReturnValue;
+ public bool GetPublishActionCalled;
+
+ public Action GetExecutionStrategy()
+ {
+ GetPublishActionCalled = true;
+ return GetPublishActionReturnValue;
+ }
+
+ public SubscriptionToken SubscriptionToken { get; set; }
+ }
+
+ class Payload { }
+
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestEventSubscription.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestEventSubscription.cs
new file mode 100644
index 0000000000..dc78cdfa0a
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestEventSubscription.cs
@@ -0,0 +1,347 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+ [TestFixture]
+ public class TestEventSubscription
+ {
+ [Test]
+ public void NullTargetInActionThrows()
+ {
+ Assert.Throws(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = null
+ };
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = (Predicate)(arg =>
+ {
+ return true;
+ })
+ };
+ var eventSubscription = new EventSubscription(actionDelegateReference,
+ filterDelegateReference);
+ });
+
+ }
+
+ [Test]
+ public void NullTargetInActionThrowsNonGeneric()
+ {
+ Assert.Throws(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = null
+ };
+ var eventSubscription = new EventSubscription(actionDelegateReference);
+ });
+ }
+
+ [Test]
+ public void DifferentTargetTypeInActionThrows()
+ {
+ Assert.Throws(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action)delegate { }
+ };
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = (Predicate)(arg =>
+ {
+ return true;
+ })
+ };
+ var eventSubscription = new EventSubscription(actionDelegateReference,
+ filterDelegateReference);
+ });
+ }
+
+ [Test]
+ public void DifferentTargetTypeInActionThrowsNonGeneric()
+ {
+ Assert.Throws(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action)delegate { }
+ };
+
+ var eventSubscription = new EventSubscription(actionDelegateReference);
+ });
+ }
+
+ [Test]
+ public void NullActionThrows()
+ {
+ Assert.Throws(() =>
+ {
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = (Predicate)(arg =>
+ {
+ return true;
+ })
+ };
+ var eventSubscription = new EventSubscription(null, filterDelegateReference);
+ });
+ }
+
+ [Test]
+ public void NullActionThrowsNonGeneric()
+ {
+ Assert.Throws(() =>
+ {
+ var eventSubscription = new EventSubscription(null);
+ });
+ }
+
+ [Test]
+ public void NullTargetInFilterThrows()
+ {
+ Assert.Throws(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action)delegate { }
+ };
+
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = null
+ };
+ var eventSubscription = new EventSubscription(actionDelegateReference,
+ filterDelegateReference);
+ });
+ }
+
+
+ [Test]
+ public void DifferentTargetTypeInFilterThrows()
+ {
+ Assert.Throws(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action)delegate { }
+ };
+
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = (Predicate)(arg =>
+ {
+ return true;
+ })
+ };
+
+ var eventSubscription = new EventSubscription(actionDelegateReference,
+ filterDelegateReference);
+ });
+ }
+
+ [Test]
+ public void NullFilterThrows()
+ {
+ Assert.Throws(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action)delegate { }
+ };
+
+ var eventSubscription = new EventSubscription(actionDelegateReference,
+ null);
+ });
+ }
+
+ [Test]
+ public void CanInitEventSubscription()
+ {
+ var actionDelegateReference = new MockDelegateReference((Action)delegate { });
+ var filterDelegateReference = new MockDelegateReference((Predicate)delegate { return true; });
+ var eventSubscription = new EventSubscription(actionDelegateReference, filterDelegateReference);
+
+ var subscriptionToken = new SubscriptionToken(t => { });
+
+ eventSubscription.SubscriptionToken = subscriptionToken;
+
+ Assert.AreSame(actionDelegateReference.Target, eventSubscription.Action);
+ Assert.AreSame(filterDelegateReference.Target, eventSubscription.Filter);
+ Assert.AreSame(subscriptionToken, eventSubscription.SubscriptionToken);
+ }
+
+ [Test]
+ public void CanInitEventSubscriptionNonGeneric()
+ {
+ var actionDelegateReference = new MockDelegateReference((Action)delegate { });
+ var eventSubscription = new EventSubscription(actionDelegateReference);
+
+ var subscriptionToken = new SubscriptionToken(t => { });
+
+ eventSubscription.SubscriptionToken = subscriptionToken;
+
+ Assert.AreSame(actionDelegateReference.Target, eventSubscription.Action);
+ Assert.AreSame(subscriptionToken, eventSubscription.SubscriptionToken);
+ }
+
+ [Test]
+ public void GetPublishActionReturnsDelegateThatExecutesTheFilterAndThenTheAction()
+ {
+ var executedDelegates = new List();
+ var actionDelegateReference =
+ new MockDelegateReference((Action)delegate { executedDelegates.Add("Action"); });
+
+ var filterDelegateReference = new MockDelegateReference((Predicate)delegate
+ {
+ executedDelegates.Add(
+ "Filter");
+ return true;
+ });
+
+ var eventSubscription = new EventSubscription(actionDelegateReference, filterDelegateReference);
+
+
+ var publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.NotNull(publishAction);
+
+ publishAction.Invoke(null);
+
+ Assert.AreEqual(2, executedDelegates.Count);
+ Assert.AreEqual("Filter", executedDelegates[0]);
+ Assert.AreEqual("Action", executedDelegates[1]);
+ }
+
+ [Test]
+ public void GetPublishActionReturnsNullIfActionIsNull()
+ {
+ var actionDelegateReference = new MockDelegateReference((Action)delegate { });
+ var filterDelegateReference = new MockDelegateReference((Predicate)delegate { return true; });
+
+ var eventSubscription = new EventSubscription(actionDelegateReference, filterDelegateReference);
+
+ var publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.NotNull(publishAction);
+
+ actionDelegateReference.Target = null;
+
+ publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.Null(publishAction);
+ }
+
+ [Test]
+ public void GetPublishActionReturnsNullIfActionIsNullNonGeneric()
+ {
+ var actionDelegateReference = new MockDelegateReference((Action)delegate { });
+
+ var eventSubscription = new EventSubscription(actionDelegateReference);
+
+ var publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.NotNull(publishAction);
+
+ actionDelegateReference.Target = null;
+
+ publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.Null(publishAction);
+ }
+
+ [Test]
+ public void GetPublishActionReturnsNullIfFilterIsNull()
+ {
+ var actionDelegateReference = new MockDelegateReference((Action)delegate { });
+ var filterDelegateReference = new MockDelegateReference((Predicate)delegate { return true; });
+
+ var eventSubscription = new EventSubscription(actionDelegateReference, filterDelegateReference);
+
+ var publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.NotNull(publishAction);
+
+ filterDelegateReference.Target = null;
+
+ publishAction = eventSubscription.GetExecutionStrategy();
+
+ Assert.Null(publishAction);
+ }
+
+ [Test]
+ public void GetPublishActionDoesNotExecuteActionIfFilterReturnsFalse()
+ {
+ bool actionExecuted = false;
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action)delegate { actionExecuted = true; }
+ };
+ var filterDelegateReference = new MockDelegateReference((Predicate)delegate
+ {
+ return false;
+ });
+
+ var eventSubscription = new EventSubscription(actionDelegateReference, filterDelegateReference);
+
+
+ var publishAction = eventSubscription.GetExecutionStrategy();
+
+ publishAction.Invoke(new object[] { null });
+
+ Assert.False(actionExecuted);
+ }
+
+ [Test]
+ public void StrategyPassesArgumentToDelegates()
+ {
+ string passedArgumentToAction = null;
+ string passedArgumentToFilter = null;
+
+ var actionDelegateReference = new MockDelegateReference((Action)(obj => passedArgumentToAction = obj));
+ var filterDelegateReference = new MockDelegateReference((Predicate)(obj =>
+ {
+ passedArgumentToFilter = obj;
+ return true;
+ }));
+
+ var eventSubscription = new EventSubscription(actionDelegateReference, filterDelegateReference);
+ var publishAction = eventSubscription.GetExecutionStrategy();
+
+ publishAction.Invoke(new[] { "TestString" });
+
+ Assert.AreEqual("TestString", passedArgumentToAction);
+ Assert.AreEqual("TestString", passedArgumentToFilter);
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestPubSubEvent.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestPubSubEvent.cs
new file mode 100644
index 0000000000..710545ecc9
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestPubSubEvent.cs
@@ -0,0 +1,712 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+ [TestFixture]
+ public class TestPubSubEvent
+ {
+ [Test]
+ public void EnsureSubscriptionListIsEmptyAfterPublishingAMessage()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ SubscribeExternalActionWithoutReference(pubSubEvent);
+ GC.Collect();
+ pubSubEvent.Publish("testPayload");
+ Assert.True(pubSubEvent.BaseSubscriptions.Count == 0, "Subscriptionlist is not empty");
+ }
+
+ [Test]
+ public void EnsureSubscriptionListIsNotEmptyWithoutPublishOrSubscribe()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ SubscribeExternalActionWithoutReference(pubSubEvent);
+ GC.Collect();
+ Assert.True(pubSubEvent.BaseSubscriptions.Count == 1, "Subscriptionlist is empty");
+ }
+
+ [Test]
+ public void EnsureSubscriptionListIsEmptyAfterSubscribeAgainAMessage()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ SubscribeExternalActionWithoutReference(pubSubEvent);
+ GC.Collect();
+ SubscribeExternalActionWithoutReference(pubSubEvent);
+ pubSubEvent.Prune();
+ Assert.True(pubSubEvent.BaseSubscriptions.Count == 1, "Subscriptionlist is empty");
+ }
+
+ private static void SubscribeExternalActionWithoutReference(TestablePubSubEvent pubSubEvent)
+ {
+ pubSubEvent.Subscribe(new ExternalAction().ExecuteAction);
+ }
+
+
+ [Test]
+ public void CanSubscribeAndRaiseEvent()
+ {
+ TestablePubSubEvent pubSubEvent = new TestablePubSubEvent();
+ bool published = false;
+ pubSubEvent.Subscribe(delegate { published = true; }, ThreadOption.PublisherThread, true, delegate { return true; });
+ pubSubEvent.Publish(null);
+
+ Assert.True(published);
+ }
+
+ [Test]
+ public void CanSubscribeAndRaiseEventNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ bool published = false;
+ pubSubEvent.Subscribe(delegate { published = true; }, ThreadOption.PublisherThread, true);
+ pubSubEvent.Publish();
+
+ Assert.True(published);
+ }
+
+ [Test]
+ public void CanSubscribeAndRaiseCustomEvent()
+ {
+ var customEvent = new TestablePubSubEvent();
+ Payload payload = new Payload();
+ var action = new ActionHelper();
+ customEvent.Subscribe(action.Action);
+
+ customEvent.Publish(payload);
+
+ Assert.AreSame(action.ActionArg(), payload);
+ }
+
+ [Test]
+ public void CanHaveMultipleSubscribersAndRaiseCustomEvent()
+ {
+ var customEvent = new TestablePubSubEvent();
+ Payload payload = new Payload();
+ var action1 = new ActionHelper();
+ var action2 = new ActionHelper();
+ customEvent.Subscribe(action1.Action);
+ customEvent.Subscribe(action2.Action);
+
+ customEvent.Publish(payload);
+
+ Assert.AreSame(action1.ActionArg(), payload);
+ Assert.AreSame(action2.ActionArg(), payload);
+ }
+
+ [Test]
+ public void CanHaveMultipleSubscribersAndRaiseEvent()
+ {
+ var customEvent = new TestablePubSubEvent();
+ var action1 = new ActionHelper();
+ var action2 = new ActionHelper();
+ customEvent.Subscribe(action1.Action);
+ customEvent.Subscribe(action2.Action);
+
+ customEvent.Publish();
+
+ Assert.True(action1.ActionCalled);
+ Assert.True(action2.ActionCalled);
+ }
+
+ [Test]
+ public void SubscribeTakesExecuteDelegateThreadOptionAndFilter()
+ {
+ TestablePubSubEvent pubSubEvent = new TestablePubSubEvent();
+ var action = new ActionHelper();
+ pubSubEvent.Subscribe(action.Action);
+
+ pubSubEvent.Publish("test");
+
+ Assert.AreEqual("test", action.ActionArg());
+
+ }
+
+ [Test]
+ public void FilterEnablesActionTarget()
+ {
+ TestablePubSubEvent pubSubEvent = new TestablePubSubEvent();
+ var goodFilter = new MockFilter { FilterReturnValue = true };
+ var actionGoodFilter = new ActionHelper();
+ var badFilter = new MockFilter { FilterReturnValue = false };
+ var actionBadFilter = new ActionHelper();
+ pubSubEvent.Subscribe(actionGoodFilter.Action, ThreadOption.PublisherThread, true, goodFilter.FilterString);
+ pubSubEvent.Subscribe(actionBadFilter.Action, ThreadOption.PublisherThread, true, badFilter.FilterString);
+
+ pubSubEvent.Publish("test");
+
+ Assert.True(actionGoodFilter.ActionCalled);
+ Assert.False(actionBadFilter.ActionCalled);
+
+ }
+
+ [Test]
+ public void FilterEnablesActionTarget_Weak()
+ {
+ TestablePubSubEvent pubSubEvent = new TestablePubSubEvent();
+ var goodFilter = new MockFilter { FilterReturnValue = true };
+ var actionGoodFilter = new ActionHelper();
+ var badFilter = new MockFilter { FilterReturnValue = false };
+ var actionBadFilter = new ActionHelper();
+ pubSubEvent.Subscribe(actionGoodFilter.Action, goodFilter.FilterString);
+ pubSubEvent.Subscribe(actionBadFilter.Action, badFilter.FilterString);
+
+ pubSubEvent.Publish("test");
+
+ Assert.True(actionGoodFilter.ActionCalled);
+ Assert.False(actionBadFilter.ActionCalled);
+
+ }
+
+ [Test]
+ public void SubscribeDefaultsThreadOptionAndNoFilter()
+ {
+ TestablePubSubEvent pubSubEvent = new TestablePubSubEvent();
+ SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+ SynchronizationContext calledSyncContext = null;
+ var myAction = new ActionHelper()
+ {
+ ActionToExecute =
+ () => calledSyncContext = SynchronizationContext.Current
+ };
+ pubSubEvent.Subscribe(myAction.Action);
+
+ pubSubEvent.Publish("test");
+
+ Assert.AreEqual(SynchronizationContext.Current, calledSyncContext);
+ }
+
+ [Test]
+ public void SubscribeDefaultsThreadOptionAndNoFilterNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+ SynchronizationContext calledSyncContext = null;
+ var myAction = new ActionHelper()
+ {
+ ActionToExecute =
+ () => calledSyncContext = SynchronizationContext.Current
+ };
+ pubSubEvent.Subscribe(myAction.Action);
+
+ pubSubEvent.Publish();
+
+ Assert.AreEqual(SynchronizationContext.Current, calledSyncContext);
+ }
+
+ [Test]
+ public void ShouldUnsubscribeFromPublisherThread()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+
+ var actionEvent = new ActionHelper();
+ PubSubEvent.Subscribe(
+ actionEvent.Action,
+ ThreadOption.PublisherThread);
+
+ Assert.True(PubSubEvent.Contains(actionEvent.Action));
+ PubSubEvent.Unsubscribe(actionEvent.Action);
+ Assert.False(PubSubEvent.Contains(actionEvent.Action));
+ }
+
+ [Test]
+ public void ShouldUnsubscribeFromPublisherThreadNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+
+ var actionEvent = new ActionHelper();
+ pubSubEvent.Subscribe(
+ actionEvent.Action,
+ ThreadOption.PublisherThread);
+
+ Assert.True(pubSubEvent.Contains(actionEvent.Action));
+ pubSubEvent.Unsubscribe(actionEvent.Action);
+ Assert.False(pubSubEvent.Contains(actionEvent.Action));
+ }
+
+ [Test]
+ public void UnsubscribeShouldNotFailWithNonSubscriber()
+ {
+ TestablePubSubEvent pubSubEvent = new TestablePubSubEvent();
+
+ Action subscriber = delegate { };
+ pubSubEvent.Unsubscribe(subscriber);
+ }
+
+ [Test]
+ public void UnsubscribeShouldNotFailWithNonSubscriberNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+
+ Action subscriber = delegate { };
+ pubSubEvent.Unsubscribe(subscriber);
+ }
+
+ [Test]
+ public void ShouldUnsubscribeFromBackgroundThread()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+
+ var actionEvent = new ActionHelper();
+ PubSubEvent.Subscribe(
+ actionEvent.Action,
+ ThreadOption.BackgroundThread);
+
+ Assert.True(PubSubEvent.Contains(actionEvent.Action));
+ PubSubEvent.Unsubscribe(actionEvent.Action);
+ Assert.False(PubSubEvent.Contains(actionEvent.Action));
+ }
+
+ [Test]
+ public void ShouldUnsubscribeFromBackgroundThreadNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+
+ var actionEvent = new ActionHelper();
+ pubSubEvent.Subscribe(
+ actionEvent.Action,
+ ThreadOption.BackgroundThread);
+
+ Assert.True(pubSubEvent.Contains(actionEvent.Action));
+ pubSubEvent.Unsubscribe(actionEvent.Action);
+ Assert.False(pubSubEvent.Contains(actionEvent.Action));
+ }
+
+ [Test]
+ public void ShouldUnsubscribeFromUIThread()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+ PubSubEvent.SynchronizationContext = new SynchronizationContext();
+
+ var actionEvent = new ActionHelper();
+ PubSubEvent.Subscribe(
+ actionEvent.Action,
+ ThreadOption.UIThread);
+
+ Assert.True(PubSubEvent.Contains(actionEvent.Action));
+ PubSubEvent.Unsubscribe(actionEvent.Action);
+ Assert.False(PubSubEvent.Contains(actionEvent.Action));
+ }
+
+ [Test]
+ public void ShouldUnsubscribeFromUIThreadNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ pubSubEvent.SynchronizationContext = new SynchronizationContext();
+
+ var actionEvent = new ActionHelper();
+ pubSubEvent.Subscribe(
+ actionEvent.Action,
+ ThreadOption.UIThread);
+
+ Assert.True(pubSubEvent.Contains(actionEvent.Action));
+ pubSubEvent.Unsubscribe(actionEvent.Action);
+ Assert.False(pubSubEvent.Contains(actionEvent.Action));
+ }
+
+ [Test]
+ public void ShouldUnsubscribeASingleDelegate()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+
+ int callCount = 0;
+
+ var actionEvent = new ActionHelper() { ActionToExecute = () => callCount++ };
+ PubSubEvent.Subscribe(actionEvent.Action);
+ PubSubEvent.Subscribe(actionEvent.Action);
+
+ PubSubEvent.Publish(null);
+ Assert.AreEqual(2, callCount);
+
+ callCount = 0;
+ PubSubEvent.Unsubscribe(actionEvent.Action);
+ PubSubEvent.Publish(null);
+ Assert.AreEqual(1, callCount);
+ }
+
+ [Test]
+ public void ShouldUnsubscribeASingleDelegateNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+
+ int callCount = 0;
+
+ var actionEvent = new ActionHelper() { ActionToExecute = () => callCount++ };
+ pubSubEvent.Subscribe(actionEvent.Action);
+ pubSubEvent.Subscribe(actionEvent.Action);
+
+ pubSubEvent.Publish();
+ Assert.AreEqual(2, callCount);
+
+ callCount = 0;
+ pubSubEvent.Unsubscribe(actionEvent.Action);
+ pubSubEvent.Publish();
+ Assert.AreEqual(1, callCount);
+ }
+
+ [Test]
+ public async Task ShouldNotExecuteOnGarbageCollectedDelegateReferenceWhenNotKeepAlive()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+
+ ExternalAction externalAction = new ExternalAction();
+ PubSubEvent.Subscribe(externalAction.ExecuteAction);
+
+ PubSubEvent.Publish("testPayload");
+ Assert.AreEqual("testPayload", externalAction.PassedValue);
+
+ WeakReference actionEventReference = new WeakReference(externalAction);
+ externalAction = null;
+ await Task.Delay(100);
+ GC.Collect();
+ Assert.False(actionEventReference.IsAlive);
+
+ PubSubEvent.Publish("testPayload");
+ }
+
+ [Test]
+ public async Task ShouldNotExecuteOnGarbageCollectedDelegateReferenceWhenNotKeepAliveNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+
+ var externalAction = new ExternalAction();
+ pubSubEvent.Subscribe(externalAction.ExecuteAction);
+
+ pubSubEvent.Publish();
+ Assert.True(externalAction.Executed);
+
+ var actionEventReference = new WeakReference(externalAction);
+ externalAction = null;
+ await Task.Delay(100);
+ GC.Collect();
+ Assert.False(actionEventReference.IsAlive);
+
+ pubSubEvent.Publish();
+ }
+
+ [Test]
+ public async Task ShouldNotExecuteOnGarbageCollectedFilterReferenceWhenNotKeepAlive()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+
+ bool wasCalled = false;
+ var actionEvent = new ActionHelper() { ActionToExecute = () => wasCalled = true };
+
+ ExternalFilter filter = new ExternalFilter();
+ PubSubEvent.Subscribe(actionEvent.Action, ThreadOption.PublisherThread, false, filter.AlwaysTrueFilter);
+
+ PubSubEvent.Publish("testPayload");
+ Assert.True(wasCalled);
+
+ wasCalled = false;
+ WeakReference filterReference = new WeakReference(filter);
+ filter = null;
+ await Task.Delay(100);
+ GC.Collect();
+ Assert.False(filterReference.IsAlive);
+
+ PubSubEvent.Publish("testPayload");
+ Assert.False(wasCalled);
+ }
+
+ [Test]
+ public void CanAddSubscriptionWhileEventIsFiring()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+
+ var emptyAction = new ActionHelper();
+ var subscriptionAction = new ActionHelper
+ {
+ ActionToExecute = (() =>
+ PubSubEvent.Subscribe(
+ emptyAction.Action))
+ };
+
+ PubSubEvent.Subscribe(subscriptionAction.Action);
+
+ Assert.False(PubSubEvent.Contains(emptyAction.Action));
+
+ PubSubEvent.Publish(null);
+
+ Assert.True((PubSubEvent.Contains(emptyAction.Action)));
+ }
+
+ [Test]
+ public void CanAddSubscriptionWhileEventIsFiringNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+
+ var emptyAction = new ActionHelper();
+ var subscriptionAction = new ActionHelper
+ {
+ ActionToExecute = (() =>
+ pubSubEvent.Subscribe(
+ emptyAction.Action))
+ };
+
+ pubSubEvent.Subscribe(subscriptionAction.Action);
+
+ Assert.False(pubSubEvent.Contains(emptyAction.Action));
+
+ pubSubEvent.Publish();
+
+ Assert.True((pubSubEvent.Contains(emptyAction.Action)));
+ }
+
+ [Test]
+ public void InlineDelegateDeclarationsDoesNotGetCollectedIncorrectlyWithWeakReferences()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+ bool published = false;
+ PubSubEvent.Subscribe(delegate { published = true; }, ThreadOption.PublisherThread, false, delegate { return true; });
+ GC.Collect();
+ PubSubEvent.Publish(null);
+
+ Assert.True(published);
+ }
+
+ [Test]
+ public void InlineDelegateDeclarationsDoesNotGetCollectedIncorrectlyWithWeakReferencesNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ bool published = false;
+ pubSubEvent.Subscribe(delegate { published = true; }, ThreadOption.PublisherThread, false);
+ GC.Collect();
+ pubSubEvent.Publish();
+
+ Assert.True(published);
+ }
+
+ [Test]
+ public void ShouldNotGarbageCollectDelegateReferenceWhenUsingKeepAlive()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+
+ var externalAction = new ExternalAction();
+ PubSubEvent.Subscribe(externalAction.ExecuteAction, ThreadOption.PublisherThread, true);
+
+ WeakReference actionEventReference = new WeakReference(externalAction);
+ externalAction = null;
+ GC.Collect();
+ GC.Collect();
+ Assert.True(actionEventReference.IsAlive);
+
+ PubSubEvent.Publish("testPayload");
+
+ Assert.AreEqual("testPayload", ((ExternalAction)actionEventReference.Target).PassedValue);
+ }
+
+ [Test]
+ public void ShouldNotGarbageCollectDelegateReferenceWhenUsingKeepAliveNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+
+ var externalAction = new ExternalAction();
+ pubSubEvent.Subscribe(externalAction.ExecuteAction, ThreadOption.PublisherThread, true);
+
+ WeakReference actionEventReference = new WeakReference(externalAction);
+ externalAction = null;
+ GC.Collect();
+ GC.Collect();
+ Assert.True(actionEventReference.IsAlive);
+
+ pubSubEvent.Publish();
+
+ Assert.True(((ExternalAction)actionEventReference.Target).Executed);
+ }
+
+ [Test]
+ public void RegisterReturnsTokenThatCanBeUsedToUnsubscribe()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+ var emptyAction = new ActionHelper();
+
+ var token = PubSubEvent.Subscribe(emptyAction.Action);
+ PubSubEvent.Unsubscribe(token);
+
+ Assert.False(PubSubEvent.Contains(emptyAction.Action));
+ }
+
+ [Test]
+ public void RegisterReturnsTokenThatCanBeUsedToUnsubscribeNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ var emptyAction = new ActionHelper();
+
+ var token = pubSubEvent.Subscribe(emptyAction.Action);
+ pubSubEvent.Unsubscribe(token);
+
+ Assert.False(pubSubEvent.Contains(emptyAction.Action));
+ }
+
+ [Test]
+ public void ContainsShouldSearchByToken()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+ var emptyAction = new ActionHelper();
+ var token = PubSubEvent.Subscribe(emptyAction.Action);
+
+ Assert.True(PubSubEvent.Contains(token));
+
+ PubSubEvent.Unsubscribe(emptyAction.Action);
+ Assert.False(PubSubEvent.Contains(token));
+ }
+
+ [Test]
+ public void ContainsShouldSearchByTokenNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ var emptyAction = new ActionHelper();
+ var token = pubSubEvent.Subscribe(emptyAction.Action);
+
+ Assert.True(pubSubEvent.Contains(token));
+
+ pubSubEvent.Unsubscribe(emptyAction.Action);
+ Assert.False(pubSubEvent.Contains(token));
+ }
+
+ [Test]
+ public void SubscribeDefaultsToPublisherThread()
+ {
+ var PubSubEvent = new TestablePubSubEvent();
+ Action action = delegate { };
+ var token = PubSubEvent.Subscribe(action, true);
+
+ Assert.AreEqual(1, PubSubEvent.BaseSubscriptions.Count);
+ Assert.AreEqual(typeof(EventSubscription), PubSubEvent.BaseSubscriptions.ElementAt(0).GetType());
+ }
+
+ [Test]
+ public void SubscribeDefaultsToPublisherThreadNonGeneric()
+ {
+ var pubSubEvent = new TestablePubSubEvent();
+ Action action = delegate { };
+ var token = pubSubEvent.Subscribe(action, true);
+
+ Assert.AreEqual(1, pubSubEvent.BaseSubscriptions.Count);
+ Assert.AreEqual(typeof(EventSubscription), pubSubEvent.BaseSubscriptions.ElementAt(0).GetType());
+ }
+
+ public class ExternalFilter
+ {
+ public bool AlwaysTrueFilter(string value)
+ {
+ return true;
+ }
+ }
+
+ public class ExternalAction
+ {
+ public string PassedValue;
+ public bool Executed = false;
+
+ public void ExecuteAction(string value)
+ {
+ PassedValue = value;
+ Executed = true;
+ }
+
+ public void ExecuteAction()
+ {
+ Executed = true;
+ }
+ }
+
+ class TestablePubSubEvent : PubSubEvent
+ {
+ public ICollection BaseSubscriptions
+ {
+ get { return base.Subscriptions; }
+ }
+ }
+
+ class TestablePubSubEvent : PubSubEvent
+ {
+ public ICollection BaseSubscriptions
+ {
+ get { return base.Subscriptions; }
+ }
+ }
+
+ public class Payload { }
+ }
+
+ public class ActionHelper
+ {
+ public bool ActionCalled;
+ public Action ActionToExecute = null;
+ private object actionArg;
+
+ public T ActionArg()
+ {
+ return (T)actionArg;
+ }
+
+ public void Action(TestPubSubEvent.Payload arg)
+ {
+ Action((object)arg);
+ }
+
+ public void Action(string arg)
+ {
+ Action((object)arg);
+ }
+
+ public void Action(object arg)
+ {
+ actionArg = arg;
+ ActionCalled = true;
+ if (ActionToExecute != null)
+ {
+ ActionToExecute.Invoke();
+ }
+ }
+
+ public void Action()
+ {
+ ActionCalled = true;
+ if (ActionToExecute != null)
+ {
+ ActionToExecute.Invoke();
+ }
+ }
+ }
+
+ public class MockFilter
+ {
+ public bool FilterReturnValue;
+
+ public bool FilterString(string arg)
+ {
+ return FilterReturnValue;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Index/IndexReader.cs b/src/Lucene.Net/Index/IndexReader.cs
index d1e355e0f7..f8fa305276 100644
--- a/src/Lucene.Net/Index/IndexReader.cs
+++ b/src/Lucene.Net/Index/IndexReader.cs
@@ -4,7 +4,7 @@
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Prism.Events;
+using Lucene.Net.Util.Events;
#endif
using System;
using System.Collections;
@@ -157,7 +157,7 @@ public void RegisterParentReader(IndexReader reader)
if (!parentReaders.TryGetValue(key: reader, out _))
{
parentReaders.Add(key: reader, value: null);
- reader.SubscribeToGetParentReadersEvent(eventAggregator.GetEvent());
+ reader.SubscribeToGetParentReadersEvent(eventAggregator.GetEvent());
}
#endif
}
@@ -211,8 +211,8 @@ private void ReportDisposeToParentReaders() // LUCENENET: Renamed from reportClo
{
IndexReader target = kvp.Key;
#else
- var e = new Events.GetParentReadersEventArgs();
- eventAggregator.GetEvent().Publish(e);
+ var e = new WeakEvents.GetParentReadersEventArgs();
+ eventAggregator.GetEvent().Publish(e);
foreach (var target in e.ParentReaders)
{
#endif
@@ -636,10 +636,10 @@ protected virtual void Dispose(bool disposing)
// LUCENENET specific - since .NET Standard 2.0 and .NET Framework don't have a CondtionalWeakTable enumerator,
// we use a weak event to retrieve the ConditionalWeakTable items
[ExcludeFromRamUsageEstimation]
- private readonly ISet getParentReadersEvents = new JCG.HashSet();
+ private readonly ISet getParentReadersEvents = new JCG.HashSet();
[ExcludeFromRamUsageEstimation]
- private readonly ISet getCacheKeysEvents = new JCG.HashSet();
- internal void SubscribeToGetParentReadersEvent(Events.GetParentReadersEvent getParentReadersEvent)
+ private readonly ISet getCacheKeysEvents = new JCG.HashSet();
+ internal void SubscribeToGetParentReadersEvent(WeakEvents.GetParentReadersEvent getParentReadersEvent)
{
if (getParentReadersEvent is null)
throw new ArgumentNullException(nameof(getParentReadersEvent));
@@ -647,7 +647,7 @@ internal void SubscribeToGetParentReadersEvent(Events.GetParentReadersEvent getP
getParentReadersEvent.Subscribe(OnGetParentReaders);
}
- internal void SubscribeToGetCacheKeysEvent(Events.GetCacheKeysEvent getCacheKeysEvent)
+ internal void SubscribeToGetCacheKeysEvent(WeakEvents.GetCacheKeysEvent getCacheKeysEvent)
{
if (getCacheKeysEvent is null)
throw new ArgumentNullException(nameof(getCacheKeysEvent));
@@ -662,12 +662,12 @@ internal void SubscribeToGetCacheKeysEvent(Events.GetCacheKeysEvent getCacheKeys
}
// LUCENENET specific: Add weak event handler for .NET Standard 2.0 and .NET Framework, since we don't have an enumerator to use
- private void OnGetParentReaders(Events.GetParentReadersEventArgs e)
+ private void OnGetParentReaders(WeakEvents.GetParentReadersEventArgs e)
{
e.ParentReaders.Add(this);
}
- private void OnGetCacheKeys(Events.GetCacheKeysEventArgs e)
+ private void OnGetCacheKeys(WeakEvents.GetCacheKeysEventArgs e)
{
e.CacheKeys.Add(this.CoreCacheKey);
}
diff --git a/src/Lucene.Net/Lucene.Net.csproj b/src/Lucene.Net/Lucene.Net.csproj
index 3175904396..a1889b96bd 100644
--- a/src/Lucene.Net/Lucene.Net.csproj
+++ b/src/Lucene.Net/Lucene.Net.csproj
@@ -59,12 +59,10 @@
-
-
diff --git a/src/Lucene.Net/Search/CachingWrapperFilter.cs b/src/Lucene.Net/Search/CachingWrapperFilter.cs
index b65a47e05d..ece5aab600 100644
--- a/src/Lucene.Net/Search/CachingWrapperFilter.cs
+++ b/src/Lucene.Net/Search/CachingWrapperFilter.cs
@@ -3,7 +3,7 @@
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Prism.Events;
+using Lucene.Net.Util.Events;
#endif
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -136,7 +136,7 @@ public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDo
cache.AddOrUpdate(key, docIdSet);
// LUCENENET specific - since .NET Standard 2.0 and .NET Framework don't have a CondtionalWeakTable enumerator,
// we use a weak event to retrieve the DocIdSet instances
- reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent());
+ reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent());
}
finally
{
@@ -200,8 +200,8 @@ public virtual long GetSizeInBytes()
// we use a weak event to retrieve the DocIdSet instances. We look each of these up here to avoid the need
// to attach events to the DocIdSet instances themselves (thus using the existing IndexReader.Dispose()
// method to detach the events rather than using a finalizer in DocIdSet to ensure they are cleaned up).
- var e = new Events.GetCacheKeysEventArgs();
- eventAggregator.GetEvent().Publish(e);
+ var e = new WeakEvents.GetCacheKeysEventArgs();
+ eventAggregator.GetEvent().Publish(e);
foreach (var key in e.CacheKeys)
if (cache.TryGetValue(key, out DocIdSet value))
docIdSets.Add(value);
diff --git a/src/Lucene.Net/Search/FieldCacheImpl.cs b/src/Lucene.Net/Search/FieldCacheImpl.cs
index 07e490a3dc..5f40e42034 100644
--- a/src/Lucene.Net/Search/FieldCacheImpl.cs
+++ b/src/Lucene.Net/Search/FieldCacheImpl.cs
@@ -7,7 +7,7 @@
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Prism.Events;
+using Lucene.Net.Util.Events;
#endif
using System;
using System.Collections.Generic;
@@ -188,8 +188,8 @@ private void AddCacheEntries(IList result,
#else
// LUCENENET specific - since .NET Standard 2.0 and .NET Framework don't have a CondtionalWeakTable enumerator,
// we use a weak event to retrieve the readerKey instances and then lookup the values in the table one by one.
- var e = new Events.GetCacheKeysEventArgs();
- eventAggregator.GetEvent().Publish(e);
+ var e = new WeakEvents.GetCacheKeysEventArgs();
+ eventAggregator.GetEvent().Publish(e);
foreach (object readerKey in e.CacheKeys)
{
if (cache.readerCache.TryGetValue(readerKey, out IDictionary innerCache))
@@ -270,7 +270,7 @@ private void InitReader(AtomicReader reader)
#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
// LUCENENET specific - since .NET Standard 2.0 and .NET Framework don't have a CondtionalWeakTable enumerator,
// we use a weak event to retrieve the readerKey instances
- reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent());
+ reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent());
#endif
}
diff --git a/src/Lucene.Net/Support/Util/Events/BackgroundEventSubscription.cs b/src/Lucene.Net/Support/Util/Events/BackgroundEventSubscription.cs
new file mode 100644
index 0000000000..ced968e082
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/BackgroundEventSubscription.cs
@@ -0,0 +1,84 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Threading.Tasks;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Extends to invoke the delegate in a background thread.
+ ///
+ internal class BackgroundEventSubscription : EventSubscription
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// A reference to a delegate of type .
+ /// When or are .
+ /// When the target of is not of type .
+ public BackgroundEventSubscription(IDelegateReference actionReference)
+ : base(actionReference)
+ {
+ }
+
+ ///
+ /// Invokes the specified in an asynchronous thread by using a .
+ ///
+ /// The action to execute.
+ public override void InvokeAction(Action action)
+ {
+ Task.Run(action);
+ }
+ }
+ ///
+ /// Extends to invoke the delegate in a background thread.
+ ///
+ /// The type to use for the generic and types.
+ internal class BackgroundEventSubscription : EventSubscription
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// A reference to a delegate of type .
+ /// A reference to a delegate of type .
+ /// When or are .
+ /// When the target of is not of type ,
+ /// or the target of is not of type .
+ public BackgroundEventSubscription(IDelegateReference actionReference, IDelegateReference filterReference)
+ : base(actionReference, filterReference)
+ {
+ }
+
+ ///
+ /// Invokes the specified in an asynchronous thread by using a .
+ ///
+ /// The action to execute.
+ /// The payload to pass while invoking it.
+ public override void InvokeAction(Action action, TPayload argument)
+ {
+ //ThreadPool.QueueUserWorkItem( (o) => action(argument) );
+ Task.Run(() => action(argument));
+ }
+ }
+}
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/DelegateReference.cs b/src/Lucene.Net/Support/Util/Events/DelegateReference.cs
new file mode 100644
index 0000000000..5ab8bfd416
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/DelegateReference.cs
@@ -0,0 +1,117 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Reflection;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Represents a reference to a that may contain a
+ /// to the target. This class is used
+ /// internally.
+ ///
+ internal class DelegateReference : IDelegateReference
+ {
+ private readonly Delegate _delegate;
+ private readonly WeakReference _weakReference;
+ private readonly MethodInfo _method;
+ private readonly Type _delegateType;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The original to create a reference for.
+ /// If the class will create a weak reference to the delegate, allowing it to be garbage collected. Otherwise it will keep a strong reference to the target.
+ /// If the passed is not assignable to .
+ public DelegateReference(Delegate @delegate, bool keepReferenceAlive)
+ {
+ if (@delegate == null)
+ throw new ArgumentNullException("delegate");
+
+ if (keepReferenceAlive)
+ {
+ this._delegate = @delegate;
+ }
+ else
+ {
+ _weakReference = new WeakReference(@delegate.Target);
+ _method = @delegate.GetMethodInfo();
+ _delegateType = @delegate.GetType();
+ }
+ }
+
+ ///
+ /// Gets the (the target) referenced by the current object.
+ ///
+ /// if the object referenced by the current object has been garbage collected; otherwise, a reference to the referenced by the current object.
+ public Delegate Target
+ {
+ get
+ {
+ if (_delegate != null)
+ {
+ return _delegate;
+ }
+ else
+ {
+ return TryGetDelegate();
+ }
+ }
+ }
+
+ ///
+ /// Checks if the (the target) referenced by the current object are equal to another .
+ /// This is equivalent with comparing with , only more efficient.
+ ///
+ /// The other delegate to compare with.
+ /// True if the target referenced by the current object are equal to .
+ public bool TargetEquals(Delegate @delegate)
+ {
+ if (_delegate != null)
+ {
+ return _delegate == @delegate;
+ }
+ if (@delegate == null)
+ {
+ return !_method.IsStatic && !_weakReference.IsAlive;
+ }
+ return _weakReference.Target == @delegate.Target && Equals(_method, @delegate.GetMethodInfo());
+ }
+
+ private Delegate TryGetDelegate()
+ {
+ if (_method.IsStatic)
+ {
+ return _method.CreateDelegate(_delegateType, null);
+ }
+ object target = _weakReference.Target;
+ if (target != null)
+ {
+ return _method.CreateDelegate(_delegateType, target);
+ }
+ return null;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/DispatcherEventSubscription.cs b/src/Lucene.Net/Support/Util/Events/DispatcherEventSubscription.cs
new file mode 100644
index 0000000000..b56ee88470
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/DispatcherEventSubscription.cs
@@ -0,0 +1,95 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Threading;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Extends to invoke the delegate
+ /// in a specific .
+ ///
+ internal class DispatcherEventSubscription : EventSubscription
+ {
+ private readonly SynchronizationContext syncContext;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// A reference to a delegate of type .
+ /// The synchronization context to use for UI thread dispatching.
+ ///When or are .
+ ///When the target of is not of type .
+ public DispatcherEventSubscription(IDelegateReference actionReference, SynchronizationContext context)
+ : base(actionReference)
+ {
+ syncContext = context;
+ }
+
+ ///
+ /// Invokes the specified asynchronously in the specified .
+ ///
+ /// The action to execute.
+ public override void InvokeAction(Action action)
+ {
+ syncContext.Post((o) => action(), null);
+ }
+ }
+
+ ///
+ /// Extends to invoke the delegate
+ /// in a specific .
+ ///
+ /// The type to use for the generic and types.
+ internal class DispatcherEventSubscription : EventSubscription
+ {
+ private readonly SynchronizationContext syncContext;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// A reference to a delegate of type .
+ /// A reference to a delegate of type .
+ /// The synchronization context to use for UI thread dispatching.
+ ///When or are .
+ ///When the target of is not of type ,
+ ///or the target of is not of type .
+ public DispatcherEventSubscription(IDelegateReference actionReference, IDelegateReference filterReference, SynchronizationContext context)
+ : base(actionReference, filterReference)
+ {
+ syncContext = context;
+ }
+
+ ///
+ /// Invokes the specified asynchronously in the specified .
+ ///
+ /// The action to execute.
+ /// The payload to pass while invoking it.
+ public override void InvokeAction(Action action, TPayload argument)
+ {
+ syncContext.Post((o) => action((TPayload)o), argument);
+ }
+ }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/EventAggregator.cs b/src/Lucene.Net/Support/Util/Events/EventAggregator.cs
new file mode 100644
index 0000000000..0c753b16c5
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/EventAggregator.cs
@@ -0,0 +1,68 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Implements .
+ ///
+ internal class EventAggregator : IEventAggregator
+ {
+ private readonly Dictionary events = new Dictionary();
+ // Captures the sync context for the UI thread when constructed on the UI thread
+ // in a platform agnostic way so it can be used for UI thread dispatching
+ private readonly SynchronizationContext syncContext = SynchronizationContext.Current;
+
+ ///
+ /// Gets the single instance of the event managed by this EventAggregator. Multiple calls to this method with the same returns the same event instance.
+ ///
+ /// The type of event to get. This must inherit from .
+ /// A singleton instance of an event object of type .
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
+ public TEventType GetEvent() where TEventType : EventBase, new()
+ {
+ lock (events)
+ {
+ EventBase existingEvent = null;
+
+ if (!events.TryGetValue(typeof(TEventType), out existingEvent))
+ {
+ TEventType newEvent = new TEventType();
+ newEvent.SynchronizationContext = syncContext;
+ events[typeof(TEventType)] = newEvent;
+
+ return newEvent;
+ }
+ else
+ {
+ return (TEventType)existingEvent;
+ }
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/EventBase.cs b/src/Lucene.Net/Support/Util/Events/EventBase.cs
new file mode 100644
index 0000000000..7d087d5051
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/EventBase.cs
@@ -0,0 +1,163 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Defines a base class to publish and subscribe to events.
+ ///
+ internal abstract class EventBase
+ {
+ private readonly List _subscriptions = new List();
+
+ ///
+ /// Allows the SynchronizationContext to be set by the EventAggregator for UI Thread Dispatching
+ ///
+ public SynchronizationContext SynchronizationContext { get; set; }
+
+ ///
+ /// Gets the list of current subscriptions.
+ ///
+ /// The current subscribers.
+ protected ICollection Subscriptions
+ {
+ get { return _subscriptions; }
+ }
+
+ ///
+ /// Adds the specified to the subscribers' collection.
+ ///
+ /// The subscriber.
+ /// The that uniquely identifies every subscriber.
+ ///
+ /// Adds the subscription to the internal list and assigns it a new .
+ ///
+ protected virtual SubscriptionToken InternalSubscribe(IEventSubscription eventSubscription)
+ {
+ if (eventSubscription == null) throw new ArgumentNullException(nameof(eventSubscription));
+
+ eventSubscription.SubscriptionToken = new SubscriptionToken(Unsubscribe);
+
+ lock (Subscriptions)
+ {
+ Subscriptions.Add(eventSubscription);
+ }
+ return eventSubscription.SubscriptionToken;
+ }
+
+ ///
+ /// Calls all the execution strategies exposed by the list of .
+ ///
+ /// The arguments that will be passed to the listeners.
+ /// Before executing the strategies, this class will prune all the subscribers from the
+ /// list that return a when calling the
+ /// method.
+ protected virtual void InternalPublish(params object[] arguments)
+ {
+ List> executionStrategies = PruneAndReturnStrategies();
+ foreach (var executionStrategy in executionStrategies)
+ {
+ executionStrategy(arguments);
+ }
+ }
+
+ ///
+ /// Removes the subscriber matching the .
+ ///
+ /// The returned by while subscribing to the event.
+ public virtual void Unsubscribe(SubscriptionToken token)
+ {
+ lock (Subscriptions)
+ {
+ IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
+ if (subscription != null)
+ {
+ Subscriptions.Remove(subscription);
+ }
+ }
+ }
+
+ ///
+ /// Returns if there is a subscriber matching .
+ ///
+ /// The returned by while subscribing to the event.
+ /// if there is a that matches; otherwise .
+ public virtual bool Contains(SubscriptionToken token)
+ {
+ lock (Subscriptions)
+ {
+ IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
+ return subscription != null;
+ }
+ }
+
+ private List> PruneAndReturnStrategies()
+ {
+ List> returnList = new List>();
+
+ lock (Subscriptions)
+ {
+ for (var i = Subscriptions.Count - 1; i >= 0; i--)
+ {
+ Action listItem =
+ _subscriptions[i].GetExecutionStrategy();
+
+ if (listItem == null)
+ {
+ // Prune from main list. Log?
+ _subscriptions.RemoveAt(i);
+ }
+ else
+ {
+ returnList.Add(listItem);
+ }
+ }
+ }
+
+ return returnList;
+ }
+
+ ///
+ /// Forces the PubSubEvent to remove any subscriptions that no longer have an execution strategy.
+ ///
+ public void Prune()
+ {
+ lock (Subscriptions)
+ {
+ for (var i = Subscriptions.Count - 1; i >= 0; i--)
+ {
+ if (_subscriptions[i].GetExecutionStrategy() == null)
+ {
+ _subscriptions.RemoveAt(i);
+ }
+ }
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/EventSubscription.cs b/src/Lucene.Net/Support/Util/Events/EventSubscription.cs
new file mode 100644
index 0000000000..d5ee36290e
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/EventSubscription.cs
@@ -0,0 +1,214 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Globalization;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Provides a way to retrieve a to execute an action depending
+ /// on the value of a second filter predicate that returns true if the action should execute.
+ ///
+ internal class EventSubscription : IEventSubscription
+ {
+ private readonly IDelegateReference _actionReference;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// A reference to a delegate of type .
+ ///When or are .
+ ///When the target of is not of type .
+ public EventSubscription(IDelegateReference actionReference)
+ {
+ if (actionReference == null)
+ throw new ArgumentNullException(nameof(actionReference));
+ if (!(actionReference.Target is Action))
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidDelegateRerefenceTypeException, typeof(Action).FullName), nameof(actionReference));
+
+ _actionReference = actionReference;
+ }
+
+ ///
+ /// Gets the target that is referenced by the .
+ ///
+ /// An or if the referenced target is not alive.
+ public Action Action
+ {
+ get { return (Action)_actionReference.Target; }
+ }
+
+ ///
+ /// Gets or sets a that identifies this .
+ ///
+ /// A token that identifies this .
+ public SubscriptionToken SubscriptionToken { get; set; }
+
+ ///
+ /// Gets the execution strategy to publish this event.
+ ///
+ /// An with the execution strategy, or if the is no longer valid.
+ ///
+ /// If is no longer valid because it was
+ /// garbage collected, this method will return .
+ /// Otherwise it will return a delegate that evaluates the and if it
+ /// returns will then call . The returned
+ /// delegate holds a hard reference to the target
+ /// delegates . As long as the returned delegate is not garbage collected,
+ /// the references delegates won't get collected either.
+ ///
+ public virtual Action GetExecutionStrategy()
+ {
+ Action action = this.Action;
+ if (action != null)
+ {
+ return arguments =>
+ {
+ InvokeAction(action);
+ };
+ }
+ return null;
+ }
+
+ ///
+ /// Invokes the specified synchronously when not overridden.
+ ///
+ /// The action to execute.
+ /// An is thrown if is null.
+ public virtual void InvokeAction(Action action)
+ {
+ if (action == null) throw new ArgumentNullException(nameof(action));
+
+ action();
+ }
+ }
+
+ ///
+ /// Provides a way to retrieve a to execute an action depending
+ /// on the value of a second filter predicate that returns true if the action should execute.
+ ///
+ /// The type to use for the generic and types.
+ internal class EventSubscription : IEventSubscription
+ {
+ private readonly IDelegateReference _actionReference;
+ private readonly IDelegateReference _filterReference;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// A reference to a delegate of type .
+ /// A reference to a delegate of type .
+ ///When or are .
+ ///When the target of is not of type ,
+ ///or the target of is not of type .
+ public EventSubscription(IDelegateReference actionReference, IDelegateReference filterReference)
+ {
+ if (actionReference == null)
+ throw new ArgumentNullException(nameof(actionReference));
+ if (!(actionReference.Target is Action))
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidDelegateRerefenceTypeException, typeof(Action).FullName), nameof(actionReference));
+
+ if (filterReference == null)
+ throw new ArgumentNullException(nameof(filterReference));
+ if (!(filterReference.Target is Predicate))
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidDelegateRerefenceTypeException, typeof(Predicate).FullName), nameof(filterReference));
+
+ _actionReference = actionReference;
+ _filterReference = filterReference;
+ }
+
+ ///
+ /// Gets the target that is referenced by the .
+ ///
+ /// An or if the referenced target is not alive.
+ public Action Action
+ {
+ get { return (Action)_actionReference.Target; }
+ }
+
+ ///
+ /// Gets the target that is referenced by the .
+ ///
+ /// An or if the referenced target is not alive.
+ public Predicate Filter
+ {
+ get { return (Predicate)_filterReference.Target; }
+ }
+
+ ///
+ /// Gets or sets a that identifies this .
+ ///
+ /// A token that identifies this .
+ public SubscriptionToken SubscriptionToken { get; set; }
+
+ ///
+ /// Gets the execution strategy to publish this event.
+ ///
+ /// An with the execution strategy, or if the is no longer valid.
+ ///
+ /// If or are no longer valid because they were
+ /// garbage collected, this method will return .
+ /// Otherwise it will return a delegate that evaluates the and if it
+ /// returns will then call . The returned
+ /// delegate holds hard references to the and target
+ /// delegates . As long as the returned delegate is not garbage collected,
+ /// the and references delegates won't get collected either.
+ ///
+ public virtual Action GetExecutionStrategy()
+ {
+ Action action = this.Action;
+ Predicate filter = this.Filter;
+ if (action != null && filter != null)
+ {
+ return arguments =>
+ {
+ TPayload argument = default(TPayload);
+ if (arguments != null && arguments.Length > 0 && arguments[0] != null)
+ {
+ argument = (TPayload)arguments[0];
+ }
+ if (filter(argument))
+ {
+ InvokeAction(action, argument);
+ }
+ };
+ }
+ return null;
+ }
+
+ ///
+ /// Invokes the specified synchronously when not overridden.
+ ///
+ /// The action to execute.
+ /// The payload to pass while invoking it.
+ /// An is thrown if is null.
+ public virtual void InvokeAction(Action action, TPayload argument)
+ {
+ if (action == null) throw new ArgumentNullException(nameof(action));
+
+ action(argument);
+ }
+ }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/IDelegateReference.cs b/src/Lucene.Net/Support/Util/Events/IDelegateReference.cs
new file mode 100644
index 0000000000..003cf52d30
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/IDelegateReference.cs
@@ -0,0 +1,40 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Represents a reference to a .
+ ///
+ internal interface IDelegateReference
+ {
+ ///
+ /// Gets the referenced object.
+ ///
+ /// A instance if the target is valid; otherwise .
+ Delegate Target { get; }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/IEventAggregator.cs b/src/Lucene.Net/Support/Util/Events/IEventAggregator.cs
new file mode 100644
index 0000000000..585e69b153
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/IEventAggregator.cs
@@ -0,0 +1,40 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Defines an interface to get instances of an event type.
+ ///
+ internal interface IEventAggregator
+ {
+ ///
+ /// Gets an instance of an event type.
+ ///
+ /// The type of event to get.
+ /// An instance of an event object of type .
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
+ TEventType GetEvent() where TEventType : EventBase, new();
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/IEventSubscription.cs b/src/Lucene.Net/Support/Util/Events/IEventSubscription.cs
new file mode 100644
index 0000000000..c34fed1384
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/IEventSubscription.cs
@@ -0,0 +1,47 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Defines a contract for an event subscription to be used by .
+ ///
+ internal interface IEventSubscription
+ {
+ ///
+ /// Gets or sets a that identifies this .
+ ///
+ /// A token that identifies this .
+ SubscriptionToken SubscriptionToken { get; set; }
+
+ ///
+ /// Gets the execution strategy to publish this event.
+ ///
+ /// An with the execution strategy, or if the is no longer valid.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ Action GetExecutionStrategy();
+ }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/PubSubEvent.cs b/src/Lucene.Net/Support/Util/Events/PubSubEvent.cs
new file mode 100644
index 0000000000..d90f41234b
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/PubSubEvent.cs
@@ -0,0 +1,329 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Defines a class that manages publication and subscription to events.
+ ///
+ internal class PubSubEvent : EventBase
+ {
+ ///
+ /// Subscribes a delegate to an event that will be published on the .
+ /// will maintain a to the target of the supplied delegate.
+ ///
+ /// The delegate that gets executed when the event is published.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public SubscriptionToken Subscribe(Action action)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread);
+ }
+
+ ///
+ /// Subscribes a delegate to an event.
+ /// PubSubEvent will maintain a to the Target of the supplied delegate.
+ ///
+ /// The delegate that gets executed when the event is raised.
+ /// Specifies on which thread to receive the delegate callback.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public SubscriptionToken Subscribe(Action action, ThreadOption threadOption)
+ {
+ return Subscribe(action, threadOption, false);
+ }
+
+ ///
+ /// Subscribes a delegate to an event that will be published on the .
+ ///
+ /// The delegate that gets executed when the event is published.
+ /// When , the keeps a reference to the subscriber so it does not get garbage collected.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// If is set to , will maintain a to the Target of the supplied delegate.
+ /// If not using a WeakReference ( is ), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public SubscriptionToken Subscribe(Action action, bool keepSubscriberReferenceAlive)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);
+ }
+
+ ///
+ /// Subscribes a delegate to an event.
+ ///
+ /// The delegate that gets executed when the event is published.
+ /// Specifies on which thread to receive the delegate callback.
+ /// When , the keeps a reference to the subscriber so it does not get garbage collected.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// If is set to , will maintain a to the Target of the supplied delegate.
+ /// If not using a WeakReference ( is ), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public virtual SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
+ {
+ IDelegateReference actionReference = new DelegateReference(action, keepSubscriberReferenceAlive);
+
+ EventSubscription subscription;
+ switch (threadOption)
+ {
+ case ThreadOption.PublisherThread:
+ subscription = new EventSubscription(actionReference);
+ break;
+ case ThreadOption.BackgroundThread:
+ subscription = new BackgroundEventSubscription(actionReference);
+ break;
+ case ThreadOption.UIThread:
+ if (SynchronizationContext == null) throw new InvalidOperationException(Resources.EventAggregatorNotConstructedOnUIThread);
+ subscription = new DispatcherEventSubscription(actionReference, SynchronizationContext);
+ break;
+ default:
+ subscription = new EventSubscription(actionReference);
+ break;
+ }
+
+ return InternalSubscribe(subscription);
+ }
+
+ ///
+ /// Publishes the .
+ ///
+ public virtual void Publish()
+ {
+ InternalPublish();
+ }
+
+ ///
+ /// Removes the first subscriber matching from the subscribers' list.
+ ///
+ /// The used when subscribing to the event.
+ public virtual void Unsubscribe(Action subscriber)
+ {
+ lock (Subscriptions)
+ {
+ IEventSubscription eventSubscription = Subscriptions.Cast().FirstOrDefault(evt => evt.Action == subscriber);
+ if (eventSubscription != null)
+ {
+ Subscriptions.Remove(eventSubscription);
+ }
+ }
+ }
+
+ ///
+ /// Returns if there is a subscriber matching .
+ ///
+ /// The used when subscribing to the event.
+ /// if there is an that matches; otherwise .
+ public virtual bool Contains(Action subscriber)
+ {
+ IEventSubscription eventSubscription;
+ lock (Subscriptions)
+ {
+ eventSubscription = Subscriptions.Cast().FirstOrDefault(evt => evt.Action == subscriber);
+ }
+ return eventSubscription != null;
+ }
+ }
+
+ ///
+ /// Defines a class that manages publication and subscription to events.
+ ///
+ /// The type of message that will be passed to the subscribers.
+ internal class PubSubEvent : EventBase
+ {
+ ///
+ /// Subscribes a delegate to an event that will be published on the .
+ /// will maintain a to the target of the supplied delegate.
+ ///
+ /// The delegate that gets executed when the event is published.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public SubscriptionToken Subscribe(Action action)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread);
+ }
+
+ ///
+ /// Subscribes a delegate to an event that will be published on the
+ ///
+ /// The delegate that gets executed when the event is raised.
+ /// Filter to evaluate if the subscriber should receive the event.
+ /// A that uniquely identifies the added subscription.
+ public virtual SubscriptionToken Subscribe(Action action, Predicate filter)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread, false, filter);
+ }
+
+ ///
+ /// Subscribes a delegate to an event.
+ /// PubSubEvent will maintain a to the Target of the supplied delegate.
+ ///
+ /// The delegate that gets executed when the event is raised.
+ /// Specifies on which thread to receive the delegate callback.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public SubscriptionToken Subscribe(Action action, ThreadOption threadOption)
+ {
+ return Subscribe(action, threadOption, false);
+ }
+
+ ///
+ /// Subscribes a delegate to an event that will be published on the .
+ ///
+ /// The delegate that gets executed when the event is published.
+ /// When , the keeps a reference to the subscriber so it does not get garbage collected.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// If is set to , will maintain a to the Target of the supplied delegate.
+ /// If not using a WeakReference ( is ), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public SubscriptionToken Subscribe(Action action, bool keepSubscriberReferenceAlive)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);
+ }
+
+ ///
+ /// Subscribes a delegate to an event.
+ ///
+ /// The delegate that gets executed when the event is published.
+ /// Specifies on which thread to receive the delegate callback.
+ /// When , the keeps a reference to the subscriber so it does not get garbage collected.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// If is set to , will maintain a to the Target of the supplied delegate.
+ /// If not using a WeakReference ( is ), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
+ {
+ return Subscribe(action, threadOption, keepSubscriberReferenceAlive, null);
+ }
+
+ ///
+ /// Subscribes a delegate to an event.
+ ///
+ /// The delegate that gets executed when the event is published.
+ /// Specifies on which thread to receive the delegate callback.
+ /// When , the keeps a reference to the subscriber so it does not get garbage collected.
+ /// Filter to evaluate if the subscriber should receive the event.
+ /// A that uniquely identifies the added subscription.
+ ///
+ /// If is set to , will maintain a to the Target of the supplied delegate.
+ /// If not using a WeakReference ( is ), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ ///
+ /// The PubSubEvent collection is thread-safe.
+ ///
+ public virtual SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate filter)
+ {
+ IDelegateReference actionReference = new DelegateReference(action, keepSubscriberReferenceAlive);
+ IDelegateReference filterReference;
+ if (filter != null)
+ {
+ filterReference = new DelegateReference(filter, keepSubscriberReferenceAlive);
+ }
+ else
+ {
+ filterReference = new DelegateReference(new Predicate(delegate { return true; }), true);
+ }
+ EventSubscription subscription;
+ switch (threadOption)
+ {
+ case ThreadOption.PublisherThread:
+ subscription = new EventSubscription(actionReference, filterReference);
+ break;
+ case ThreadOption.BackgroundThread:
+ subscription = new BackgroundEventSubscription(actionReference, filterReference);
+ break;
+ case ThreadOption.UIThread:
+ if (SynchronizationContext == null) throw new InvalidOperationException(Resources.EventAggregatorNotConstructedOnUIThread);
+ subscription = new DispatcherEventSubscription(actionReference, filterReference, SynchronizationContext);
+ break;
+ default:
+ subscription = new EventSubscription(actionReference, filterReference);
+ break;
+ }
+
+ return InternalSubscribe(subscription);
+ }
+
+ ///
+ /// Publishes the .
+ ///
+ /// Message to pass to the subscribers.
+ public virtual void Publish(TPayload payload)
+ {
+ InternalPublish(payload);
+ }
+
+ ///
+ /// Removes the first subscriber matching from the subscribers' list.
+ ///
+ /// The used when subscribing to the event.
+ public virtual void Unsubscribe(Action subscriber)
+ {
+ lock (Subscriptions)
+ {
+ IEventSubscription eventSubscription = Subscriptions.Cast>().FirstOrDefault(evt => evt.Action == subscriber);
+ if (eventSubscription != null)
+ {
+ Subscriptions.Remove(eventSubscription);
+ }
+ }
+ }
+
+ ///
+ /// Returns if there is a subscriber matching .
+ ///
+ /// The used when subscribing to the event.
+ /// if there is an that matches; otherwise .
+ public virtual bool Contains(Action subscriber)
+ {
+ IEventSubscription eventSubscription;
+ lock (Subscriptions)
+ {
+ eventSubscription = Subscriptions.Cast>().FirstOrDefault(evt => evt.Action == subscriber);
+ }
+ return eventSubscription != null;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/Resources.cs b/src/Lucene.Net/Support/Util/Events/Resources.cs
new file mode 100644
index 0000000000..04e9d28ec9
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/Resources.cs
@@ -0,0 +1,32 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+namespace Lucene.Net.Util.Events
+{
+ internal static class Resources
+ {
+ public static string EventAggregatorNotConstructedOnUIThread = "To use the UIThread option for subscribing, the EventAggregator must be constructed on the UI thread.";
+ public static string InvalidDelegateRerefenceTypeException = "Invalid Delegate Reference Type Exception";
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/SubscriptionToken.cs b/src/Lucene.Net/Support/Util/Events/SubscriptionToken.cs
new file mode 100644
index 0000000000..e422eb3bb9
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/SubscriptionToken.cs
@@ -0,0 +1,106 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Subscription token returned from on subscribe.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Should never have a need for a finalizer, hence no need for Dispose(bool)")]
+ internal class SubscriptionToken
+ {
+ private readonly Guid _token;
+ private Action _unsubscribeAction;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public SubscriptionToken(Action unsubscribeAction)
+ {
+ _unsubscribeAction = unsubscribeAction;
+ _token = Guid.NewGuid();
+ }
+
+ ///
+ ///Indicates whether the current object is equal to another object of the same type.
+ ///
+ ///
+ /// if the current object is equal to the parameter; otherwise, .
+ ///
+ /// An object to compare with this object.
+ public bool Equals(SubscriptionToken other)
+ {
+ if (other == null) return false;
+ return Equals(_token, other._token);
+ }
+
+ ///
+ ///Determines whether the specified is equal to the current .
+ ///
+ ///
+ ///true if the specified is equal to the current ; otherwise, false.
+ ///
+ /// The to compare with the current .
+ ///The parameter is null. 2
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(this, obj)) return true;
+ return Equals(obj as SubscriptionToken);
+ }
+
+ ///
+ /// Serves as a hash function for a particular type.
+ ///
+ ///
+ /// A hash code for the current .
+ ///
+ /// 2
+ public override int GetHashCode()
+ {
+ return _token.GetHashCode();
+ }
+
+ ///
+ /// Disposes the SubscriptionToken, removing the subscription from the corresponding .
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Should never have need for a finalizer, hence no need for Dispose(bool).")]
+ public virtual void Dispose()
+ {
+ // While the SubscriptionToken class implements IDisposable, in the case of weak subscriptions
+ // (i.e. keepSubscriberReferenceAlive set to false in the Subscribe method) it's not necessary to unsubscribe,
+ // as no resources should be kept alive by the event subscription.
+ // In such cases, if a warning is issued, it could be suppressed.
+
+ if (this._unsubscribeAction != null)
+ {
+ this._unsubscribeAction(this);
+ this._unsubscribeAction = null;
+ }
+
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/ThreadOption.cs b/src/Lucene.Net/Support/Util/Events/ThreadOption.cs
new file mode 100644
index 0000000000..3043c2110b
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/ThreadOption.cs
@@ -0,0 +1,47 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Specifies on which thread a subscriber will be called.
+ ///
+ internal enum ThreadOption
+ {
+ ///
+ /// The call is done on the same thread on which the was published.
+ ///
+ PublisherThread,
+
+ ///
+ /// The call is done on the UI thread.
+ ///
+ UIThread,
+
+ ///
+ /// The call is done asynchronously on a background thread.
+ ///
+ BackgroundThread
+ }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/WeakDelegatesManager.cs b/src/Lucene.Net/Support/Util/Events/WeakDelegatesManager.cs
new file mode 100644
index 0000000000..5f171fe483
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/WeakDelegatesManager.cs
@@ -0,0 +1,71 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Lucene.Net.Util.Events
+{
+ ///
+ /// Manage delegates using weak references to prevent keeping target instances longer than expected.
+ ///
+ internal class WeakDelegatesManager
+ {
+ private readonly List _listeners = new List();
+
+ ///
+ /// Adds a weak reference to the specified listener.
+ ///
+ /// The original to add.
+ public void AddListener(Delegate listener)
+ {
+ _listeners.Add(new DelegateReference(listener, false));
+ }
+
+ ///
+ /// Removes the weak reference to the specified listener.
+ ///
+ /// The original to remove.
+ public void RemoveListener(Delegate listener)
+ {
+ //Remove the listener, and prune collected listeners
+ _listeners.RemoveAll(reference => reference.TargetEquals(null) || reference.TargetEquals(listener));
+ }
+
+ ///
+ /// Invoke the delegates for all targets still being alive.
+ ///
+ /// An array of objects that are the arguments to pass to the delegates. -or- null, if the method represented by the delegate does not require arguments.
+ public void Raise(params object[] args)
+ {
+ _listeners.RemoveAll(listener => listener.TargetEquals(null));
+
+ foreach (Delegate handler in _listeners.Select(listener => listener.Target).Where(listener => listener != null).ToList())
+ {
+ handler.DynamicInvoke(args);
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events.cs b/src/Lucene.Net/Support/Util/WeakEvents.cs
similarity index 97%
rename from src/Lucene.Net/Support/Util/Events.cs
rename to src/Lucene.Net/Support/Util/WeakEvents.cs
index 3d1203cdf3..365c847263 100644
--- a/src/Lucene.Net/Support/Util/Events.cs
+++ b/src/Lucene.Net/Support/Util/WeakEvents.cs
@@ -1,6 +1,6 @@
#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
using Lucene.Net.Index;
-using Prism.Events;
+using Lucene.Net.Util.Events;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -27,7 +27,7 @@ namespace Lucene.Net.Util
/// Events are used in Lucene.NET to work around the fact that
/// doesn't have an enumerator in .NET Framework or .NET Standard prior to 2.1. They are declared in this static class to avoid adding coupling.
///
- internal static class Events
+ internal static class WeakEvents
{
#region GetParentReaders