You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2023/10/18 23:46:54 UTC
[lucenenet] branch master updated: Removed dependency on Prism.Core (Fixes #872) (#875)
This is an automated email from the ASF dual-hosted git repository.
nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git
The following commit(s) were added to refs/heads/master by this push:
new 96b68625c Removed dependency on Prism.Core (Fixes #872) (#875)
96b68625c is described below
commit 96b68625cf7289672285376d1d188f9e33f22ddd
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Thu Oct 19 06:46:47 2023 +0700
Removed dependency on Prism.Core (Fixes #872) (#875)
* Lucene.Net.Util: Renamed Events class > WeakEvents
* SWEEP: Removed dependency on Prism.Core and brought the Prism.Events namespace into Lucene.Net.Util.Events including tests
---
.build/dependencies.props | 1 -
.rat-excludes | 1 +
.../Taxonomy/CachedOrdinalsReader.cs | 8 +-
.../Support/Util/Events/MockDelegateReference.cs | 43 ++
.../Util/Events/TestBackgroundEventSubscription.cs | 91 +++
.../Support/Util/Events/TestDelegateReference.cs | 214 +++++++
.../Util/Events/TestDispatcherEventSubscription.cs | 123 ++++
.../Support/Util/Events/TestEventAggregator.cs | 45 ++
.../Support/Util/Events/TestEventBase.cs | 138 ++++
.../Support/Util/Events/TestEventSubscription.cs | 347 ++++++++++
.../Support/Util/Events/TestPubSubEvent.cs | 712 +++++++++++++++++++++
src/Lucene.Net/Index/IndexReader.cs | 20 +-
src/Lucene.Net/Lucene.Net.csproj | 2 -
src/Lucene.Net/Search/CachingWrapperFilter.cs | 8 +-
src/Lucene.Net/Search/FieldCacheImpl.cs | 8 +-
.../Util/Events/BackgroundEventSubscription.cs | 84 +++
.../Support/Util/Events/DelegateReference.cs | 117 ++++
.../Util/Events/DispatcherEventSubscription.cs | 95 +++
.../Support/Util/Events/EventAggregator.cs | 68 ++
src/Lucene.Net/Support/Util/Events/EventBase.cs | 163 +++++
.../Support/Util/Events/EventSubscription.cs | 214 +++++++
.../Support/Util/Events/IDelegateReference.cs | 40 ++
.../Support/Util/Events/IEventAggregator.cs | 40 ++
.../Support/Util/Events/IEventSubscription.cs | 47 ++
src/Lucene.Net/Support/Util/Events/PubSubEvent.cs | 329 ++++++++++
src/Lucene.Net/Support/Util/Events/Resources.cs | 32 +
.../Support/Util/Events/SubscriptionToken.cs | 106 +++
src/Lucene.Net/Support/Util/Events/ThreadOption.cs | 47 ++
.../Support/Util/Events/WeakDelegatesManager.cs | 71 ++
.../Support/Util/{Events.cs => WeakEvents.cs} | 4 +-
30 files changed, 3191 insertions(+), 27 deletions(-)
diff --git a/.build/dependencies.props b/.build/dependencies.props
index f97e58774..cac54299e 100644
--- a/.build/dependencies.props
+++ b/.build/dependencies.props
@@ -69,7 +69,6 @@
<NUnit3TestAdapterPackageVersion>3.17.0</NUnit3TestAdapterPackageVersion>
<NUnitPackageVersion>3.13.1</NUnitPackageVersion>
<OpenNLPNETPackageVersion>1.9.1.1</OpenNLPNETPackageVersion>
- <PrismCorePackageVersion>7.2.0.1422</PrismCorePackageVersion>
<RandomizedTestingGeneratorsPackageVersion>2.7.8</RandomizedTestingGeneratorsPackageVersion>
<SharpZipLibPackageVersion>1.1.0</SharpZipLibPackageVersion>
<Spatial4nPackageVersion>0.4.1.1</Spatial4nPackageVersion>
diff --git a/.rat-excludes b/.rat-excludes
index 257b68536..cddb9ba01 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 3d58fadc5..19f85a64d 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;
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 @@ namespace Lucene.Net.Facet.Taxonomy
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<Events.GetCacheKeysEvent>());
+ context.Reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>());
#endif
}
return ords;
@@ -229,8 +229,8 @@ namespace Lucene.Net.Facet.Taxonomy
// 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<Events.GetCacheKeysEvent>().Publish(e);
+ var e = new WeakEvents.GetCacheKeysEventArgs();
+ eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>().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 000000000..7565e2f54
--- /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 000000000..d959c297a
--- /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<object> action = delegate
+ {
+ calledSyncContext = SynchronizationContext.Current;
+ completeEvent.Set();
+ };
+
+ IDelegateReference actionDelegateReference = new MockDelegateReference() { Target = action };
+ IDelegateReference filterDelegateReference = new MockDelegateReference() { Target = (Predicate<object>)delegate { return true; } };
+
+ var eventSubscription = new BackgroundEventSubscription<object>(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 000000000..2d6ed5bfe
--- /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<string>)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<string>)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<string>)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<string> myAction = new Action<string>(classHandler.MyAction);
+
+ var weakAction = new DelegateReference(myAction, false);
+
+ ((Action<string>)weakAction.Target)("payload");
+ Assert.AreEqual("payload", classHandler.MyActionArg);
+ }
+
+ [Test]
+ public async Task ShouldAllowCollectionOfOriginalDelegate()
+ {
+ var classHandler = new SomeClassHandler();
+ Action<string> myAction = new Action<string>(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<string>)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<string>)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<string> myAction = new Action<string>(classHandler.MyAction);
+
+ var weakAction = new DelegateReference(myAction, false);
+
+ Assert.True(weakAction.TargetEquals(new Action<string>(classHandler.MyAction)));
+ }
+
+ [Test]
+ public async Task TargetEqualsNullShouldReturnTrueIfTargetNotAlive()
+ {
+ SomeClassHandler handler = new SomeClassHandler();
+ var weakHandlerRef = new WeakReference(handler);
+
+ var action = new DelegateReference((Action<string>)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<string>)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<ArgumentNullException>(() =>
+ // {
+ // 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 000000000..51eed98a8
--- /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<object> eventSubscription = null;
+
+ IDelegateReference actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action<object>)(arg =>
+ {
+ return;
+ })
+ };
+
+ IDelegateReference filterDelegateReference = new MockDelegateReference
+ {
+ Target = (Predicate<object>)(arg => true)
+ };
+ var mockSyncContext = new MockSynchronizationContext();
+
+ eventSubscription = new DispatcherEventSubscription<object>(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<object>)(arg1 =>
+ {
+ return;
+ })
+ };
+ IDelegateReference filterDelegateReference = new MockDelegateReference
+ {
+ Target = (Predicate<object>)(arg => true)
+ };
+
+ var mockSyncContext = new MockSynchronizationContext();
+
+ DispatcherEventSubscription<object> eventSubscription = new DispatcherEventSubscription<object>(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 000000000..5f945013f
--- /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<MockEventBase>();
+ var instance2 = eventAggregator.GetEvent<MockEventBase>();
+
+ 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 000000000..c71ddcb3a
--- /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<object[]> GetPublishActionReturnValue;
+ public bool GetPublishActionCalled;
+
+ public Action<object[]> 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 000000000..dc78cdfa0
--- /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<ArgumentException>(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = null
+ };
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = (Predicate<object>)(arg =>
+ {
+ return true;
+ })
+ };
+ var eventSubscription = new EventSubscription<object>(actionDelegateReference,
+ filterDelegateReference);
+ });
+
+ }
+
+ [Test]
+ public void NullTargetInActionThrowsNonGeneric()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = null
+ };
+ var eventSubscription = new EventSubscription(actionDelegateReference);
+ });
+ }
+
+ [Test]
+ public void DifferentTargetTypeInActionThrows()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action<int>)delegate { }
+ };
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = (Predicate<string>)(arg =>
+ {
+ return true;
+ })
+ };
+ var eventSubscription = new EventSubscription<string>(actionDelegateReference,
+ filterDelegateReference);
+ });
+ }
+
+ [Test]
+ public void DifferentTargetTypeInActionThrowsNonGeneric()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action<int>)delegate { }
+ };
+
+ var eventSubscription = new EventSubscription(actionDelegateReference);
+ });
+ }
+
+ [Test]
+ public void NullActionThrows()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = (Predicate<object>)(arg =>
+ {
+ return true;
+ })
+ };
+ var eventSubscription = new EventSubscription<object>(null, filterDelegateReference);
+ });
+ }
+
+ [Test]
+ public void NullActionThrowsNonGeneric()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ var eventSubscription = new EventSubscription(null);
+ });
+ }
+
+ [Test]
+ public void NullTargetInFilterThrows()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action<object>)delegate { }
+ };
+
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = null
+ };
+ var eventSubscription = new EventSubscription<object>(actionDelegateReference,
+ filterDelegateReference);
+ });
+ }
+
+
+ [Test]
+ public void DifferentTargetTypeInFilterThrows()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action<string>)delegate { }
+ };
+
+ var filterDelegateReference = new MockDelegateReference()
+ {
+ Target = (Predicate<int>)(arg =>
+ {
+ return true;
+ })
+ };
+
+ var eventSubscription = new EventSubscription<string>(actionDelegateReference,
+ filterDelegateReference);
+ });
+ }
+
+ [Test]
+ public void NullFilterThrows()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ var actionDelegateReference = new MockDelegateReference()
+ {
+ Target = (Action<object>)delegate { }
+ };
+
+ var eventSubscription = new EventSubscription<object>(actionDelegateReference,
+ null);
+ });
+ }
+
+ [Test]
+ public void CanInitEventSubscription()
+ {
+ var actionDelegateReference = new MockDelegateReference((Action<object>)delegate { });
+ var filterDelegateReference = new MockDelegateReference((Predicate<object>)delegate { return true; });
+ var eventSubscription = new EventSubscription<object>(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<string>();
+ var actionDelegateReference =
+ new MockDelegateReference((Action<object>)delegate { executedDelegates.Add("Action"); });
+
+ var filterDelegateReference = new MockDelegateReference((Predicate<object>)delegate
+ {
+ executedDelegates.Add(
+ "Filter");
+ return true;
+ });
+
+ var eventSubscription = new EventSubscription<object>(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<object>)delegate { });
+ var filterDelegateReference = new MockDelegateReference((Predicate<object>)delegate { return true; });
+
+ var eventSubscription = new EventSubscription<object>(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<object>)delegate { });
+ var filterDelegateReference = new MockDelegateReference((Predicate<object>)delegate { return true; });
+
+ var eventSubscription = new EventSubscription<object>(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<int>)delegate { actionExecuted = true; }
+ };
+ var filterDelegateReference = new MockDelegateReference((Predicate<int>)delegate
+ {
+ return false;
+ });
+
+ var eventSubscription = new EventSubscription<int>(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<string>)(obj => passedArgumentToAction = obj));
+ var filterDelegateReference = new MockDelegateReference((Predicate<string>)(obj =>
+ {
+ passedArgumentToFilter = obj;
+ return true;
+ }));
+
+ var eventSubscription = new EventSubscription<string>(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 000000000..710545ecc
--- /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<string>();
+ 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<string>();
+ SubscribeExternalActionWithoutReference(pubSubEvent);
+ GC.Collect();
+ Assert.True(pubSubEvent.BaseSubscriptions.Count == 1, "Subscriptionlist is empty");
+ }
+
+ [Test]
+ public void EnsureSubscriptionListIsEmptyAfterSubscribeAgainAMessage()
+ {
+ var pubSubEvent = new TestablePubSubEvent<string>();
+ SubscribeExternalActionWithoutReference(pubSubEvent);
+ GC.Collect();
+ SubscribeExternalActionWithoutReference(pubSubEvent);
+ pubSubEvent.Prune();
+ Assert.True(pubSubEvent.BaseSubscriptions.Count == 1, "Subscriptionlist is empty");
+ }
+
+ private static void SubscribeExternalActionWithoutReference(TestablePubSubEvent<string> pubSubEvent)
+ {
+ pubSubEvent.Subscribe(new ExternalAction().ExecuteAction);
+ }
+
+
+ [Test]
+ public void CanSubscribeAndRaiseEvent()
+ {
+ TestablePubSubEvent<string> pubSubEvent = new TestablePubSubEvent<string>();
+ 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 payload = new Payload();
+ var action = new ActionHelper();
+ customEvent.Subscribe(action.Action);
+
+ customEvent.Publish(payload);
+
+ Assert.AreSame(action.ActionArg<Payload>(), payload);
+ }
+
+ [Test]
+ public void CanHaveMultipleSubscribersAndRaiseCustomEvent()
+ {
+ var customEvent = new TestablePubSubEvent<Payload>();
+ 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>(), payload);
+ Assert.AreSame(action2.ActionArg<Payload>(), 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<string> pubSubEvent = new TestablePubSubEvent<string>();
+ var action = new ActionHelper();
+ pubSubEvent.Subscribe(action.Action);
+
+ pubSubEvent.Publish("test");
+
+ Assert.AreEqual("test", action.ActionArg<string>());
+
+ }
+
+ [Test]
+ public void FilterEnablesActionTarget()
+ {
+ TestablePubSubEvent<string> pubSubEvent = new TestablePubSubEvent<string>();
+ 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<string> pubSubEvent = new TestablePubSubEvent<string>();
+ 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<string> pubSubEvent = new TestablePubSubEvent<string>();
+ 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<string>();
+
+ 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<string> pubSubEvent = new TestablePubSubEvent<string>();
+
+ Action<string> 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<string>();
+
+ 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<string>();
+ 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<string>();
+
+ int callCount = 0;
+
+ var actionEvent = new ActionHelper() { ActionToExecute = () => callCount++ };
+ PubSubEvent.Subscribe(actionEvent.Action);
+ PubSubEvent.Subscribe(actionEvent.Action);
+
+ PubSubEvent.Publish(null);
+ Assert.AreEqual<int>(2, callCount);
+
+ callCount = 0;
+ PubSubEvent.Unsubscribe(actionEvent.Action);
+ PubSubEvent.Publish(null);
+ Assert.AreEqual<int>(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<int>(2, callCount);
+
+ callCount = 0;
+ pubSubEvent.Unsubscribe(actionEvent.Action);
+ pubSubEvent.Publish();
+ Assert.AreEqual<int>(1, callCount);
+ }
+
+ [Test]
+ public async Task ShouldNotExecuteOnGarbageCollectedDelegateReferenceWhenNotKeepAlive()
+ {
+ var PubSubEvent = new TestablePubSubEvent<string>();
+
+ 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<string>();
+
+ 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<string>();
+
+ 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<string>();
+ 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<string>();
+
+ 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<string>();
+ 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<string>();
+ 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<string>();
+ Action<string> action = delegate { };
+ var token = PubSubEvent.Subscribe(action, true);
+
+ Assert.AreEqual(1, PubSubEvent.BaseSubscriptions.Count);
+ Assert.AreEqual(typeof(EventSubscription<string>), 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<TPayload> : PubSubEvent<TPayload>
+ {
+ public ICollection<IEventSubscription> BaseSubscriptions
+ {
+ get { return base.Subscriptions; }
+ }
+ }
+
+ class TestablePubSubEvent : PubSubEvent
+ {
+ public ICollection<IEventSubscription> BaseSubscriptions
+ {
+ get { return base.Subscriptions; }
+ }
+ }
+
+ public class Payload { }
+ }
+
+ public class ActionHelper
+ {
+ public bool ActionCalled;
+ public Action ActionToExecute = null;
+ private object actionArg;
+
+ public T ActionArg<T>()
+ {
+ 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 d1e355e0f..f8fa30527 100644
--- a/src/Lucene.Net/Index/IndexReader.cs
+++ b/src/Lucene.Net/Index/IndexReader.cs
@@ -4,7 +4,7 @@ using Lucene.Net.Support;
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 @@ namespace Lucene.Net.Index
if (!parentReaders.TryGetValue(key: reader, out _))
{
parentReaders.Add(key: reader, value: null);
- reader.SubscribeToGetParentReadersEvent(eventAggregator.GetEvent<Events.GetParentReadersEvent>());
+ reader.SubscribeToGetParentReadersEvent(eventAggregator.GetEvent<WeakEvents.GetParentReadersEvent>());
}
#endif
}
@@ -211,8 +211,8 @@ namespace Lucene.Net.Index
{
IndexReader target = kvp.Key;
#else
- var e = new Events.GetParentReadersEventArgs();
- eventAggregator.GetEvent<Events.GetParentReadersEvent>().Publish(e);
+ var e = new WeakEvents.GetParentReadersEventArgs();
+ eventAggregator.GetEvent<WeakEvents.GetParentReadersEvent>().Publish(e);
foreach (var target in e.ParentReaders)
{
#endif
@@ -636,10 +636,10 @@ namespace Lucene.Net.Index
// 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<Events.GetParentReadersEvent> getParentReadersEvents = new JCG.HashSet<Events.GetParentReadersEvent>();
+ private readonly ISet<WeakEvents.GetParentReadersEvent> getParentReadersEvents = new JCG.HashSet<WeakEvents.GetParentReadersEvent>();
[ExcludeFromRamUsageEstimation]
- private readonly ISet<Events.GetCacheKeysEvent> getCacheKeysEvents = new JCG.HashSet<Events.GetCacheKeysEvent>();
- internal void SubscribeToGetParentReadersEvent(Events.GetParentReadersEvent getParentReadersEvent)
+ private readonly ISet<WeakEvents.GetCacheKeysEvent> getCacheKeysEvents = new JCG.HashSet<WeakEvents.GetCacheKeysEvent>();
+ internal void SubscribeToGetParentReadersEvent(WeakEvents.GetParentReadersEvent getParentReadersEvent)
{
if (getParentReadersEvent is null)
throw new ArgumentNullException(nameof(getParentReadersEvent));
@@ -647,7 +647,7 @@ namespace Lucene.Net.Index
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 @@ namespace Lucene.Net.Index
}
// 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 317590439..a1889b96b 100644
--- a/src/Lucene.Net/Lucene.Net.csproj
+++ b/src/Lucene.Net/Lucene.Net.csproj
@@ -59,12 +59,10 @@
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
- <PackageReference Include="Prism.Core" Version="$(PrismCorePackageVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
- <PackageReference Include="Prism.Core" Version="$(PrismCorePackageVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
</ItemGroup>
diff --git a/src/Lucene.Net/Search/CachingWrapperFilter.cs b/src/Lucene.Net/Search/CachingWrapperFilter.cs
index b65a47e05..ece5aab60 100644
--- a/src/Lucene.Net/Search/CachingWrapperFilter.cs
+++ b/src/Lucene.Net/Search/CachingWrapperFilter.cs
@@ -3,7 +3,7 @@ using Lucene.Net.Runtime.CompilerServices;
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 @@ namespace Lucene.Net.Search
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<Events.GetCacheKeysEvent>());
+ reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>());
}
finally
{
@@ -200,8 +200,8 @@ namespace Lucene.Net.Search
// 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<Events.GetCacheKeysEvent>().Publish(e);
+ var e = new WeakEvents.GetCacheKeysEventArgs();
+ eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>().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 07e490a3d..5f40e4203 100644
--- a/src/Lucene.Net/Search/FieldCacheImpl.cs
+++ b/src/Lucene.Net/Search/FieldCacheImpl.cs
@@ -7,7 +7,7 @@ using Lucene.Net.Support.IO;
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 @@ namespace Lucene.Net.Search
#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<Events.GetCacheKeysEvent>().Publish(e);
+ var e = new WeakEvents.GetCacheKeysEventArgs();
+ eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>().Publish(e);
foreach (object readerKey in e.CacheKeys)
{
if (cache.readerCache.TryGetValue(readerKey, out IDictionary<TKey, object> innerCache))
@@ -270,7 +270,7 @@ namespace Lucene.Net.Search
#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<Events.GetCacheKeysEvent>());
+ reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>());
#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 000000000..ced968e08
--- /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
+{
+ /// <summary>
+ /// Extends <see cref="EventSubscription"/> to invoke the <see cref="EventSubscription.Action"/> delegate in a background thread.
+ /// </summary>
+ internal class BackgroundEventSubscription : EventSubscription
+ {
+ /// <summary>
+ /// Creates a new instance of <see cref="BackgroundEventSubscription"/>.
+ /// </summary>
+ /// <param name="actionReference">A reference to a delegate of type <see cref="System.Action"/>.</param>
+ /// <exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+ /// <exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action"/>.</exception>
+ public BackgroundEventSubscription(IDelegateReference actionReference)
+ : base(actionReference)
+ {
+ }
+
+ /// <summary>
+ /// Invokes the specified <see cref="System.Action"/> in an asynchronous thread by using a <see cref="Task"/>.
+ /// </summary>
+ /// <param name="action">The action to execute.</param>
+ public override void InvokeAction(Action action)
+ {
+ Task.Run(action);
+ }
+ }
+ /// <summary>
+ /// Extends <see cref="EventSubscription{TPayload}"/> to invoke the <see cref="EventSubscription{TPayload}.Action"/> delegate in a background thread.
+ /// </summary>
+ /// <typeparam name="TPayload">The type to use for the generic <see cref="System.Action{TPayload}"/> and <see cref="Predicate{TPayload}"/> types.</typeparam>
+ internal class BackgroundEventSubscription<TPayload> : EventSubscription<TPayload>
+ {
+ /// <summary>
+ /// Creates a new instance of <see cref="BackgroundEventSubscription{TPayload}"/>.
+ /// </summary>
+ /// <param name="actionReference">A reference to a delegate of type <see cref="System.Action{TPayload}"/>.</param>
+ /// <param name="filterReference">A reference to a delegate of type <see cref="Predicate{TPayload}"/>.</param>
+ /// <exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+ /// <exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>,
+ /// or the target of <paramref name="filterReference"/> is not of type <see cref="Predicate{TPayload}"/>.</exception>
+ public BackgroundEventSubscription(IDelegateReference actionReference, IDelegateReference filterReference)
+ : base(actionReference, filterReference)
+ {
+ }
+
+ /// <summary>
+ /// Invokes the specified <see cref="System.Action{TPayload}"/> in an asynchronous thread by using a <see cref="System.Threading.ThreadPool"/>.
+ /// </summary>
+ /// <param name="action">The action to execute.</param>
+ /// <param name="argument">The payload to pass <paramref name="action"/> while invoking it.</param>
+ public override void InvokeAction(Action<TPayload> 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 000000000..5ab8bfd41
--- /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
+{
+ /// <summary>
+ /// Represents a reference to a <see cref="Delegate"/> that may contain a
+ /// <see cref="WeakReference"/> to the target. This class is used
+ /// internally.
+ /// </summary>
+ internal class DelegateReference : IDelegateReference
+ {
+ private readonly Delegate _delegate;
+ private readonly WeakReference _weakReference;
+ private readonly MethodInfo _method;
+ private readonly Type _delegateType;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="DelegateReference"/>.
+ /// </summary>
+ /// <param name="delegate">The original <see cref="Delegate"/> to create a reference for.</param>
+ /// <param name="keepReferenceAlive">If <see langword="false" /> 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.</param>
+ /// <exception cref="ArgumentNullException">If the passed <paramref name="delegate"/> is not assignable to <see cref="Delegate"/>.</exception>
+ 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();
+ }
+ }
+
+ /// <summary>
+ /// Gets the <see cref="Delegate" /> (the target) referenced by the current <see cref="DelegateReference"/> object.
+ /// </summary>
+ /// <value><see langword="null"/> if the object referenced by the current <see cref="DelegateReference"/> object has been garbage collected; otherwise, a reference to the <see cref="Delegate"/> referenced by the current <see cref="DelegateReference"/> object.</value>
+ public Delegate Target
+ {
+ get
+ {
+ if (_delegate != null)
+ {
+ return _delegate;
+ }
+ else
+ {
+ return TryGetDelegate();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Checks if the <see cref="Delegate" /> (the target) referenced by the current <see cref="DelegateReference"/> object are equal to another <see cref="Delegate" />.
+ /// This is equivalent with comparing <see cref="Target"/> with <paramref name="delegate"/>, only more efficient.
+ /// </summary>
+ /// <param name="delegate">The other delegate to compare with.</param>
+ /// <returns>True if the target referenced by the current object are equal to <paramref name="delegate"/>.</returns>
+ 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 000000000..b56ee8847
--- /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
+{
+ ///<summary>
+ /// Extends <see cref="EventSubscription"/> to invoke the <see cref="EventSubscription.Action"/> delegate
+ /// in a specific <see cref="SynchronizationContext"/>.
+ ///</summary>
+ internal class DispatcherEventSubscription : EventSubscription
+ {
+ private readonly SynchronizationContext syncContext;
+
+ ///<summary>
+ /// Creates a new instance of <see cref="BackgroundEventSubscription"/>.
+ ///</summary>
+ ///<param name="actionReference">A reference to a delegate of type <see cref="System.Action{TPayload}"/>.</param>
+ ///<param name="context">The synchronization context to use for UI thread dispatching.</param>
+ ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+ ///<exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>.</exception>
+ public DispatcherEventSubscription(IDelegateReference actionReference, SynchronizationContext context)
+ : base(actionReference)
+ {
+ syncContext = context;
+ }
+
+ /// <summary>
+ /// Invokes the specified <see cref="System.Action{TPayload}"/> asynchronously in the specified <see cref="SynchronizationContext"/>.
+ /// </summary>
+ /// <param name="action">The action to execute.</param>
+ public override void InvokeAction(Action action)
+ {
+ syncContext.Post((o) => action(), null);
+ }
+ }
+
+ ///<summary>
+ /// Extends <see cref="EventSubscription{TPayload}"/> to invoke the <see cref="EventSubscription{TPayload}.Action"/> delegate
+ /// in a specific <see cref="SynchronizationContext"/>.
+ ///</summary>
+ /// <typeparam name="TPayload">The type to use for the generic <see cref="System.Action{TPayload}"/> and <see cref="Predicate{TPayload}"/> types.</typeparam>
+ internal class DispatcherEventSubscription<TPayload> : EventSubscription<TPayload>
+ {
+ private readonly SynchronizationContext syncContext;
+
+ ///<summary>
+ /// Creates a new instance of <see cref="BackgroundEventSubscription{TPayload}"/>.
+ ///</summary>
+ ///<param name="actionReference">A reference to a delegate of type <see cref="System.Action{TPayload}"/>.</param>
+ ///<param name="filterReference">A reference to a delegate of type <see cref="Predicate{TPayload}"/>.</param>
+ ///<param name="context">The synchronization context to use for UI thread dispatching.</param>
+ ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+ ///<exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>,
+ ///or the target of <paramref name="filterReference"/> is not of type <see cref="Predicate{TPayload}"/>.</exception>
+ public DispatcherEventSubscription(IDelegateReference actionReference, IDelegateReference filterReference, SynchronizationContext context)
+ : base(actionReference, filterReference)
+ {
+ syncContext = context;
+ }
+
+ /// <summary>
+ /// Invokes the specified <see cref="System.Action{TPayload}"/> asynchronously in the specified <see cref="SynchronizationContext"/>.
+ /// </summary>
+ /// <param name="action">The action to execute.</param>
+ /// <param name="argument">The payload to pass <paramref name="action"/> while invoking it.</param>
+ public override void InvokeAction(Action<TPayload> 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 000000000..0c753b16c
--- /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
+{
+ /// <summary>
+ /// Implements <see cref="IEventAggregator"/>.
+ /// </summary>
+ internal class EventAggregator : IEventAggregator
+ {
+ private readonly Dictionary<Type, EventBase> events = new Dictionary<Type, EventBase>();
+ // 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;
+
+ /// <summary>
+ /// Gets the single instance of the event managed by this EventAggregator. Multiple calls to this method with the same <typeparamref name="TEventType"/> returns the same event instance.
+ /// </summary>
+ /// <typeparam name="TEventType">The type of event to get. This must inherit from <see cref="EventBase"/>.</typeparam>
+ /// <returns>A singleton instance of an event object of type <typeparamref name="TEventType"/>.</returns>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
+ public TEventType GetEvent<TEventType>() 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 000000000..7d087d505
--- /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
+{
+ ///<summary>
+ /// Defines a base class to publish and subscribe to events.
+ ///</summary>
+ internal abstract class EventBase
+ {
+ private readonly List<IEventSubscription> _subscriptions = new List<IEventSubscription>();
+
+ /// <summary>
+ /// Allows the SynchronizationContext to be set by the EventAggregator for UI Thread Dispatching
+ /// </summary>
+ public SynchronizationContext SynchronizationContext { get; set; }
+
+ /// <summary>
+ /// Gets the list of current subscriptions.
+ /// </summary>
+ /// <value>The current subscribers.</value>
+ protected ICollection<IEventSubscription> Subscriptions
+ {
+ get { return _subscriptions; }
+ }
+
+ /// <summary>
+ /// Adds the specified <see cref="IEventSubscription"/> to the subscribers' collection.
+ /// </summary>
+ /// <param name="eventSubscription">The subscriber.</param>
+ /// <returns>The <see cref="SubscriptionToken"/> that uniquely identifies every subscriber.</returns>
+ /// <remarks>
+ /// Adds the subscription to the internal list and assigns it a new <see cref="SubscriptionToken"/>.
+ /// </remarks>
+ 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;
+ }
+
+ /// <summary>
+ /// Calls all the execution strategies exposed by the list of <see cref="IEventSubscription"/>.
+ /// </summary>
+ /// <param name="arguments">The arguments that will be passed to the listeners.</param>
+ /// <remarks>Before executing the strategies, this class will prune all the subscribers from the
+ /// list that return a <see langword="null" /> <see cref="Action{T}"/> when calling the
+ /// <see cref="IEventSubscription.GetExecutionStrategy"/> method.</remarks>
+ protected virtual void InternalPublish(params object[] arguments)
+ {
+ List<Action<object[]>> executionStrategies = PruneAndReturnStrategies();
+ foreach (var executionStrategy in executionStrategies)
+ {
+ executionStrategy(arguments);
+ }
+ }
+
+ /// <summary>
+ /// Removes the subscriber matching the <see cref="SubscriptionToken"/>.
+ /// </summary>
+ /// <param name="token">The <see cref="SubscriptionToken"/> returned by <see cref="EventBase"/> while subscribing to the event.</param>
+ public virtual void Unsubscribe(SubscriptionToken token)
+ {
+ lock (Subscriptions)
+ {
+ IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
+ if (subscription != null)
+ {
+ Subscriptions.Remove(subscription);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns <see langword="true"/> if there is a subscriber matching <see cref="SubscriptionToken"/>.
+ /// </summary>
+ /// <param name="token">The <see cref="SubscriptionToken"/> returned by <see cref="EventBase"/> while subscribing to the event.</param>
+ /// <returns><see langword="true"/> if there is a <see cref="SubscriptionToken"/> that matches; otherwise <see langword="false"/>.</returns>
+ public virtual bool Contains(SubscriptionToken token)
+ {
+ lock (Subscriptions)
+ {
+ IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
+ return subscription != null;
+ }
+ }
+
+ private List<Action<object[]>> PruneAndReturnStrategies()
+ {
+ List<Action<object[]>> returnList = new List<Action<object[]>>();
+
+ lock (Subscriptions)
+ {
+ for (var i = Subscriptions.Count - 1; i >= 0; i--)
+ {
+ Action<object[]> listItem =
+ _subscriptions[i].GetExecutionStrategy();
+
+ if (listItem == null)
+ {
+ // Prune from main list. Log?
+ _subscriptions.RemoveAt(i);
+ }
+ else
+ {
+ returnList.Add(listItem);
+ }
+ }
+ }
+
+ return returnList;
+ }
+
+ /// <summary>
+ /// Forces the PubSubEvent to remove any subscriptions that no longer have an execution strategy.
+ /// </summary>
+ 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 000000000..d5ee36290
--- /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
+{
+ /// <summary>
+ /// Provides a way to retrieve a <see cref="Delegate"/> to execute an action depending
+ /// on the value of a second filter predicate that returns true if the action should execute.
+ /// </summary>
+ internal class EventSubscription : IEventSubscription
+ {
+ private readonly IDelegateReference _actionReference;
+
+ ///<summary>
+ /// Creates a new instance of <see cref="EventSubscription"/>.
+ ///</summary>
+ ///<param name="actionReference">A reference to a delegate of type <see cref="System.Action"/>.</param>
+ ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+ ///<exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action"/>.</exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Gets the target <see cref="System.Action"/> that is referenced by the <see cref="IDelegateReference"/>.
+ /// </summary>
+ /// <value>An <see cref="System.Action"/> or <see langword="null" /> if the referenced target is not alive.</value>
+ public Action Action
+ {
+ get { return (Action)_actionReference.Target; }
+ }
+
+ /// <summary>
+ /// Gets or sets a <see cref="SubscriptionToken"/> that identifies this <see cref="IEventSubscription"/>.
+ /// </summary>
+ /// <value>A token that identifies this <see cref="IEventSubscription"/>.</value>
+ public SubscriptionToken SubscriptionToken { get; set; }
+
+ /// <summary>
+ /// Gets the execution strategy to publish this event.
+ /// </summary>
+ /// <returns>An <see cref="System.Action"/> with the execution strategy, or <see langword="null" /> if the <see cref="IEventSubscription"/> is no longer valid.</returns>
+ /// <remarks>
+ /// If <see cref="Action"/>is no longer valid because it was
+ /// garbage collected, this method will return <see langword="null" />.
+ /// Otherwise it will return a delegate that evaluates the <see cref="EventSubscription{TPayload}.Filter"/> and if it
+ /// returns <see langword="true" /> will then call <see cref="InvokeAction"/>. The returned
+ /// delegate holds a hard reference to the <see cref="Action"/> target
+ /// <see cref="Delegate">delegates</see>. As long as the returned delegate is not garbage collected,
+ /// the <see cref="Action"/> references delegates won't get collected either.
+ /// </remarks>
+ public virtual Action<object[]> GetExecutionStrategy()
+ {
+ Action action = this.Action;
+ if (action != null)
+ {
+ return arguments =>
+ {
+ InvokeAction(action);
+ };
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Invokes the specified <see cref="System.Action{TPayload}"/> synchronously when not overridden.
+ /// </summary>
+ /// <param name="action">The action to execute.</param>
+ /// <exception cref="ArgumentNullException">An <see cref="ArgumentNullException"/> is thrown if <paramref name="action"/> is null.</exception>
+ public virtual void InvokeAction(Action action)
+ {
+ if (action == null) throw new ArgumentNullException(nameof(action));
+
+ action();
+ }
+ }
+
+ /// <summary>
+ /// Provides a way to retrieve a <see cref="Delegate"/> to execute an action depending
+ /// on the value of a second filter predicate that returns true if the action should execute.
+ /// </summary>
+ /// <typeparam name="TPayload">The type to use for the generic <see cref="System.Action{TPayload}"/> and <see cref="Predicate{TPayload}"/> types.</typeparam>
+ internal class EventSubscription<TPayload> : IEventSubscription
+ {
+ private readonly IDelegateReference _actionReference;
+ private readonly IDelegateReference _filterReference;
+
+ ///<summary>
+ /// Creates a new instance of <see cref="EventSubscription{TPayload}"/>.
+ ///</summary>
+ ///<param name="actionReference">A reference to a delegate of type <see cref="System.Action{TPayload}"/>.</param>
+ ///<param name="filterReference">A reference to a delegate of type <see cref="Predicate{TPayload}"/>.</param>
+ ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+ ///<exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>,
+ ///or the target of <paramref name="filterReference"/> is not of type <see cref="Predicate{TPayload}"/>.</exception>
+ public EventSubscription(IDelegateReference actionReference, IDelegateReference filterReference)
+ {
+ if (actionReference == null)
+ throw new ArgumentNullException(nameof(actionReference));
+ if (!(actionReference.Target is Action<TPayload>))
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidDelegateRerefenceTypeException, typeof(Action<TPayload>).FullName), nameof(actionReference));
+
+ if (filterReference == null)
+ throw new ArgumentNullException(nameof(filterReference));
+ if (!(filterReference.Target is Predicate<TPayload>))
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidDelegateRerefenceTypeException, typeof(Predicate<TPayload>).FullName), nameof(filterReference));
+
+ _actionReference = actionReference;
+ _filterReference = filterReference;
+ }
+
+ /// <summary>
+ /// Gets the target <see cref="System.Action{T}"/> that is referenced by the <see cref="IDelegateReference"/>.
+ /// </summary>
+ /// <value>An <see cref="System.Action{T}"/> or <see langword="null" /> if the referenced target is not alive.</value>
+ public Action<TPayload> Action
+ {
+ get { return (Action<TPayload>)_actionReference.Target; }
+ }
+
+ /// <summary>
+ /// Gets the target <see cref="Predicate{T}"/> that is referenced by the <see cref="IDelegateReference"/>.
+ /// </summary>
+ /// <value>An <see cref="Predicate{T}"/> or <see langword="null" /> if the referenced target is not alive.</value>
+ public Predicate<TPayload> Filter
+ {
+ get { return (Predicate<TPayload>)_filterReference.Target; }
+ }
+
+ /// <summary>
+ /// Gets or sets a <see cref="SubscriptionToken"/> that identifies this <see cref="IEventSubscription"/>.
+ /// </summary>
+ /// <value>A token that identifies this <see cref="IEventSubscription"/>.</value>
+ public SubscriptionToken SubscriptionToken { get; set; }
+
+ /// <summary>
+ /// Gets the execution strategy to publish this event.
+ /// </summary>
+ /// <returns>An <see cref="System.Action{T}"/> with the execution strategy, or <see langword="null" /> if the <see cref="IEventSubscription"/> is no longer valid.</returns>
+ /// <remarks>
+ /// If <see cref="Action"/> or <see cref="Filter"/> are no longer valid because they were
+ /// garbage collected, this method will return <see langword="null" />.
+ /// Otherwise it will return a delegate that evaluates the <see cref="Filter"/> and if it
+ /// returns <see langword="true" /> will then call <see cref="InvokeAction"/>. The returned
+ /// delegate holds hard references to the <see cref="Action"/> and <see cref="Filter"/> target
+ /// <see cref="Delegate">delegates</see>. As long as the returned delegate is not garbage collected,
+ /// the <see cref="Action"/> and <see cref="Filter"/> references delegates won't get collected either.
+ /// </remarks>
+ public virtual Action<object[]> GetExecutionStrategy()
+ {
+ Action<TPayload> action = this.Action;
+ Predicate<TPayload> 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;
+ }
+
+ /// <summary>
+ /// Invokes the specified <see cref="System.Action{TPayload}"/> synchronously when not overridden.
+ /// </summary>
+ /// <param name="action">The action to execute.</param>
+ /// <param name="argument">The payload to pass <paramref name="action"/> while invoking it.</param>
+ /// <exception cref="ArgumentNullException">An <see cref="ArgumentNullException"/> is thrown if <paramref name="action"/> is null.</exception>
+ public virtual void InvokeAction(Action<TPayload> 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 000000000..003cf52d3
--- /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
+{
+ /// <summary>
+ /// Represents a reference to a <see cref="Delegate"/>.
+ /// </summary>
+ internal interface IDelegateReference
+ {
+ /// <summary>
+ /// Gets the referenced <see cref="Delegate" /> object.
+ /// </summary>
+ /// <value>A <see cref="Delegate"/> instance if the target is valid; otherwise <see langword="null"/>.</value>
+ 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 000000000..585e69b15
--- /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
+{
+ /// <summary>
+ /// Defines an interface to get instances of an event type.
+ /// </summary>
+ internal interface IEventAggregator
+ {
+ /// <summary>
+ /// Gets an instance of an event type.
+ /// </summary>
+ /// <typeparam name="TEventType">The type of event to get.</typeparam>
+ /// <returns>An instance of an event object of type <typeparamref name="TEventType"/>.</returns>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
+ TEventType GetEvent<TEventType>() 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 000000000..c34fed138
--- /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
+{
+ ///<summary>
+ /// Defines a contract for an event subscription to be used by <see cref="EventBase"/>.
+ ///</summary>
+ internal interface IEventSubscription
+ {
+ /// <summary>
+ /// Gets or sets a <see cref="SubscriptionToken"/> that identifies this <see cref="IEventSubscription"/>.
+ /// </summary>
+ /// <value>A token that identifies this <see cref="IEventSubscription"/>.</value>
+ SubscriptionToken SubscriptionToken { get; set; }
+
+ /// <summary>
+ /// Gets the execution strategy to publish this event.
+ /// </summary>
+ /// <returns>An <see cref="Action{T}"/> with the execution strategy, or <see langword="null" /> if the <see cref="IEventSubscription"/> is no longer valid.</returns>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ Action<object[]> 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 000000000..d90f41234
--- /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
+{
+ /// <summary>
+ /// Defines a class that manages publication and subscription to events.
+ /// </summary>
+ internal class PubSubEvent : EventBase
+ {
+ /// <summary>
+ /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>.
+ /// <see cref="PubSubEvent"/> will maintain a <see cref="WeakReference"/> to the target of the supplied <paramref name="action"/> delegate.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is published.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// The PubSubEvent collection is thread-safe.
+ /// </remarks>
+ public SubscriptionToken Subscribe(Action action)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread);
+ }
+
+ /// <summary>
+ /// Subscribes a delegate to an event.
+ /// PubSubEvent will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is raised.</param>
+ /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// The PubSubEvent collection is thread-safe.
+ /// </remarks>
+ public SubscriptionToken Subscribe(Action action, ThreadOption threadOption)
+ {
+ return Subscribe(action, threadOption, false);
+ }
+
+ /// <summary>
+ /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is published.</param>
+ /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+ /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ /// <para/>
+ /// The PubSubEvent collection is thread-safe.
+ /// </remarks>
+ public SubscriptionToken Subscribe(Action action, bool keepSubscriberReferenceAlive)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);
+ }
+
+ /// <summary>
+ /// Subscribes a delegate to an event.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is published.</param>
+ /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+ /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+ /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ /// <para/>
+ /// The PubSubEvent collection is thread-safe.
+ /// </remarks>
+ 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);
+ }
+
+ /// <summary>
+ /// Publishes the <see cref="PubSubEvent"/>.
+ /// </summary>
+ public virtual void Publish()
+ {
+ InternalPublish();
+ }
+
+ /// <summary>
+ /// Removes the first subscriber matching <see cref="Action"/> from the subscribers' list.
+ /// </summary>
+ /// <param name="subscriber">The <see cref="Action"/> used when subscribing to the event.</param>
+ public virtual void Unsubscribe(Action subscriber)
+ {
+ lock (Subscriptions)
+ {
+ IEventSubscription eventSubscription = Subscriptions.Cast<EventSubscription>().FirstOrDefault(evt => evt.Action == subscriber);
+ if (eventSubscription != null)
+ {
+ Subscriptions.Remove(eventSubscription);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns <see langword="true"/> if there is a subscriber matching <see cref="Action"/>.
+ /// </summary>
+ /// <param name="subscriber">The <see cref="Action"/> used when subscribing to the event.</param>
+ /// <returns><see langword="true"/> if there is an <see cref="Action"/> that matches; otherwise <see langword="false"/>.</returns>
+ public virtual bool Contains(Action subscriber)
+ {
+ IEventSubscription eventSubscription;
+ lock (Subscriptions)
+ {
+ eventSubscription = Subscriptions.Cast<EventSubscription>().FirstOrDefault(evt => evt.Action == subscriber);
+ }
+ return eventSubscription != null;
+ }
+ }
+
+ /// <summary>
+ /// Defines a class that manages publication and subscription to events.
+ /// </summary>
+ /// <typeparam name="TPayload">The type of message that will be passed to the subscribers.</typeparam>
+ internal class PubSubEvent<TPayload> : EventBase
+ {
+ /// <summary>
+ /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>.
+ /// <see cref="PubSubEvent{TPayload}"/> will maintain a <see cref="WeakReference"/> to the target of the supplied <paramref name="action"/> delegate.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is published.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// The PubSubEvent collection is thread-safe.
+ /// </remarks>
+ public SubscriptionToken Subscribe(Action<TPayload> action)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread);
+ }
+
+ /// <summary>
+ /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is raised.</param>
+ /// <param name="filter">Filter to evaluate if the subscriber should receive the event.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ public virtual SubscriptionToken Subscribe(Action<TPayload> action, Predicate<TPayload> filter)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread, false, filter);
+ }
+
+ /// <summary>
+ /// Subscribes a delegate to an event.
+ /// PubSubEvent will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is raised.</param>
+ /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// The PubSubEvent collection is thread-safe.
+ /// </remarks>
+ public SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption)
+ {
+ return Subscribe(action, threadOption, false);
+ }
+
+ /// <summary>
+ /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is published.</param>
+ /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent{TPayload}"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent{TPayload}"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+ /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ /// <para/>
+ /// The PubSubEvent collection is thread-safe.
+ /// </remarks>
+ public SubscriptionToken Subscribe(Action<TPayload> action, bool keepSubscriberReferenceAlive)
+ {
+ return Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);
+ }
+
+ /// <summary>
+ /// Subscribes a delegate to an event.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is published.</param>
+ /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+ /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent{TPayload}"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent{TPayload}"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+ /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+ /// <para/>
+ /// The PubSubEvent collection is thread-safe.
+ /// </remarks>
+ public SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
+ {
+ return Subscribe(action, threadOption, keepSubscriberReferenceAlive, null);
+ }
+
+ /// <summary>
+ /// Subscribes a delegate to an event.
+ /// </summary>
+ /// <param name="action">The delegate that gets executed when the event is published.</param>
+ /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+ /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent{TPayload}"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+ /// <param name="filter">Filter to evaluate if the subscriber should receive the event.</param>
+ /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+ /// <remarks>
+ /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent{TPayload}"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+ /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), 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.
+ /// </remarks>
+ public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter)
+ {
+ IDelegateReference actionReference = new DelegateReference(action, keepSubscriberReferenceAlive);
+ IDelegateReference filterReference;
+ if (filter != null)
+ {
+ filterReference = new DelegateReference(filter, keepSubscriberReferenceAlive);
+ }
+ else
+ {
+ filterReference = new DelegateReference(new Predicate<TPayload>(delegate { return true; }), true);
+ }
+ EventSubscription<TPayload> subscription;
+ switch (threadOption)
+ {
+ case ThreadOption.PublisherThread:
+ subscription = new EventSubscription<TPayload>(actionReference, filterReference);
+ break;
+ case ThreadOption.BackgroundThread:
+ subscription = new BackgroundEventSubscription<TPayload>(actionReference, filterReference);
+ break;
+ case ThreadOption.UIThread:
+ if (SynchronizationContext == null) throw new InvalidOperationException(Resources.EventAggregatorNotConstructedOnUIThread);
+ subscription = new DispatcherEventSubscription<TPayload>(actionReference, filterReference, SynchronizationContext);
+ break;
+ default:
+ subscription = new EventSubscription<TPayload>(actionReference, filterReference);
+ break;
+ }
+
+ return InternalSubscribe(subscription);
+ }
+
+ /// <summary>
+ /// Publishes the <see cref="PubSubEvent{TPayload}"/>.
+ /// </summary>
+ /// <param name="payload">Message to pass to the subscribers.</param>
+ public virtual void Publish(TPayload payload)
+ {
+ InternalPublish(payload);
+ }
+
+ /// <summary>
+ /// Removes the first subscriber matching <see cref="Action{TPayload}"/> from the subscribers' list.
+ /// </summary>
+ /// <param name="subscriber">The <see cref="Action{TPayload}"/> used when subscribing to the event.</param>
+ public virtual void Unsubscribe(Action<TPayload> subscriber)
+ {
+ lock (Subscriptions)
+ {
+ IEventSubscription eventSubscription = Subscriptions.Cast<EventSubscription<TPayload>>().FirstOrDefault(evt => evt.Action == subscriber);
+ if (eventSubscription != null)
+ {
+ Subscriptions.Remove(eventSubscription);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns <see langword="true"/> if there is a subscriber matching <see cref="Action{TPayload}"/>.
+ /// </summary>
+ /// <param name="subscriber">The <see cref="Action{TPayload}"/> used when subscribing to the event.</param>
+ /// <returns><see langword="true"/> if there is an <see cref="Action{TPayload}"/> that matches; otherwise <see langword="false"/>.</returns>
+ public virtual bool Contains(Action<TPayload> subscriber)
+ {
+ IEventSubscription eventSubscription;
+ lock (Subscriptions)
+ {
+ eventSubscription = Subscriptions.Cast<EventSubscription<TPayload>>().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 000000000..04e9d28ec
--- /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 000000000..e422eb3bb
--- /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
+{
+ /// <summary>
+ /// Subscription token returned from <see cref="EventBase"/> on subscribe.
+ /// </summary>
+ [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<SubscriptionToken> _unsubscribeAction;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="SubscriptionToken"/>.
+ /// </summary>
+ public SubscriptionToken(Action<SubscriptionToken> unsubscribeAction)
+ {
+ _unsubscribeAction = unsubscribeAction;
+ _token = Guid.NewGuid();
+ }
+
+ ///<summary>
+ ///Indicates whether the current object is equal to another object of the same type.
+ ///</summary>
+ ///<returns>
+ ///<see langword="true"/> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <see langword="false"/>.
+ ///</returns>
+ ///<param name="other">An object to compare with this object.</param>
+ public bool Equals(SubscriptionToken other)
+ {
+ if (other == null) return false;
+ return Equals(_token, other._token);
+ }
+
+ ///<summary>
+ ///Determines whether the specified <see cref="T:System.Object" /> is equal to the current <see cref="T:System.Object" />.
+ ///</summary>
+ ///<returns>
+ ///true if the specified <see cref="T:System.Object" /> is equal to the current <see cref="T:System.Object" />; otherwise, false.
+ ///</returns>
+ ///<param name="obj">The <see cref="T:System.Object" /> to compare with the current <see cref="T:System.Object" />. </param>
+ ///<exception cref="T:System.NullReferenceException">The <paramref name="obj" /> parameter is null.</exception><filterpriority>2</filterpriority>
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(this, obj)) return true;
+ return Equals(obj as SubscriptionToken);
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object" />.
+ /// </returns>
+ /// <filterpriority>2</filterpriority>
+ public override int GetHashCode()
+ {
+ return _token.GetHashCode();
+ }
+
+ /// <summary>
+ /// Disposes the SubscriptionToken, removing the subscription from the corresponding <see cref="EventBase"/>.
+ /// </summary>
+ [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 000000000..3043c2110
--- /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
+{
+ /// <summary>
+ /// Specifies on which thread a <see cref="PubSubEvent{TPayload}"/> subscriber will be called.
+ /// </summary>
+ internal enum ThreadOption
+ {
+ /// <summary>
+ /// The call is done on the same thread on which the <see cref="PubSubEvent{TPayload}"/> was published.
+ /// </summary>
+ PublisherThread,
+
+ /// <summary>
+ /// The call is done on the UI thread.
+ /// </summary>
+ UIThread,
+
+ /// <summary>
+ /// The call is done asynchronously on a background thread.
+ /// </summary>
+ 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 000000000..5f171fe48
--- /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
+{
+ /// <summary>
+ /// Manage delegates using weak references to prevent keeping target instances longer than expected.
+ /// </summary>
+ internal class WeakDelegatesManager
+ {
+ private readonly List<DelegateReference> _listeners = new List<DelegateReference>();
+
+ /// <summary>
+ /// Adds a weak reference to the specified <see cref="Delegate"/> listener.
+ /// </summary>
+ /// <param name="listener">The original <see cref="Delegate"/> to add.</param>
+ public void AddListener(Delegate listener)
+ {
+ _listeners.Add(new DelegateReference(listener, false));
+ }
+
+ /// <summary>
+ /// Removes the weak reference to the specified <see cref="Delegate"/> listener.
+ /// </summary>
+ /// <param name="listener">The original <see cref="Delegate"/> to remove.</param>
+ public void RemoveListener(Delegate listener)
+ {
+ //Remove the listener, and prune collected listeners
+ _listeners.RemoveAll(reference => reference.TargetEquals(null) || reference.TargetEquals(listener));
+ }
+
+ /// <summary>
+ /// Invoke the delegates for all targets still being alive.
+ /// </summary>
+ /// <param name="args">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. </param>
+ 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 96%
rename from src/Lucene.Net/Support/Util/Events.cs
rename to src/Lucene.Net/Support/Util/WeakEvents.cs
index 3d1203cdf..365c84726 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 <see cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey, TValue}"/>
/// 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.
/// </summary>
- internal static class Events
+ internal static class WeakEvents
{
#region GetParentReaders