From c151c256caaff094146dfc467f116470b77866e6 Mon Sep 17 00:00:00 2001 From: Alexandr Pliushchou Date: Fri, 29 Nov 2024 15:52:06 +0300 Subject: [PATCH 1/3] Make EventManager thread-safe DEVSIX-8640 --- .../datastructures/ConcurrentHashSet.cs | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 itext/itext.commons/itext/commons/datastructures/ConcurrentHashSet.cs diff --git a/itext/itext.commons/itext/commons/datastructures/ConcurrentHashSet.cs b/itext/itext.commons/itext/commons/datastructures/ConcurrentHashSet.cs new file mode 100644 index 0000000000..f5e81651d6 --- /dev/null +++ b/itext/itext.commons/itext/commons/datastructures/ConcurrentHashSet.cs @@ -0,0 +1,216 @@ +/* + This file is part of the iText (R) project. + Copyright (c) 1998-2024 Apryse Group NV + Authors: Apryse Software. + + This program is offered under a commercial and under the AGPL license. + For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + + AGPL licensing: + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ +using System; +using System.Collections.Concurrent; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections.ObjectModel; +using iText.Commons.Utils; +using iText.Commons.Exceptions; + +namespace iText.Commons.Datastructures +{ + public class ConcurrentHashSet : ISet + { + private readonly ConcurrentDictionary dictionary; + + /// + public ConcurrentHashSet(IEnumerable elements = null) + { + dictionary = new ConcurrentDictionary(); + if (elements != null) { + UnionWith(elements); + } + } + + /// + public void UnionWith(IEnumerable elements) + { + if (elements == null) + { + throw new ArgumentNullException("other"); + } + + foreach (var otherElement in elements) + { + Add(otherElement); + } + } + + /// + public bool Add(TElement item) + { + return dictionary.TryAdd(item, null); + } + + /// + public void Clear() + { + dictionary.Clear(); + } + + /// + public bool Contains(TElement item) + { + return dictionary.ContainsKey(item); + } + + /// + public void CopyTo(TElement[] array, int arrayIndex) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + public bool Remove(TElement item) + { + object ignoredObject; + return dictionary.TryRemove(item, out ignoredObject); + } + + + public void ForEach(Action action) + { + foreach (TElement item in dictionary.Keys) + { + action(item); + } + } + + /// + public int Count + { + get { return dictionary.Count; } + } + + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + public IEnumerator GetEnumerator() + { + return dictionary.Keys.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return dictionary.Keys.GetEnumerator(); + } + + public void AddAll(IEnumerable elements) + { + UnionWith(elements); + } + + public void RemoveAll(IEnumerable elements) + { + ExceptWith(elements); + } + + public void ContainsAll(IEnumerable elements) + { + IsSupersetOf(elements); + } + + public bool Equals(IEnumerable elements) + { + return dictionary.Keys.Equals(elements); + } + + public int HashCode() + { + return dictionary.Keys.GetHashCode(); + } + + /// + public void IntersectWith(IEnumerable elements) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + public void ExceptWith(IEnumerable elements) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + public void SymmetricExceptWith(IEnumerable elements) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + public bool IsSubsetOf(IEnumerable elements) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + public bool IsSupersetOf(IEnumerable elements) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + public bool IsProperSupersetOf(IEnumerable elements) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + public bool IsProperSubsetOf(IEnumerable elements) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + void ICollection.Add(TElement item) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + /// + public bool Overlaps(IEnumerable elements) + { + return elements.Any(element => dictionary.ContainsKey(element)); + } + + /// + public bool SetEquals(IEnumerable other) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + + public bool RetainAll(IEnumerable other) + { + throw new NotSupportedException(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION); + } + } +} From f7ac2d96ea0fef1cc63fa26834765cb5aff87cc1 Mon Sep 17 00:00:00 2001 From: Alexandr Pliushchou Date: Wed, 11 Dec 2024 16:52:20 +0000 Subject: [PATCH 2/3] Make EventManager thread-safe DEVSIX-8640 Autoported commit. Original commit hash: [0a8638bed] Manual files: commons/src/main/java/com/itextpdf/commons/datastructures/ConcurrentHashSet.java sharpenConfiguration.xml --- .../itext/commons/actions/EventManagerTest.cs | 17 ++- .../datastructures/ConcurrentHashSetTest.cs | 127 ++++++++++++++++++ .../itext/commons/actions/EventManager.cs | 7 +- .../CommonsExceptionMessageConstant.cs | 2 + port-hash | 2 +- 5 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 itext.tests/itext.commons.tests/itext/commons/datastructures/ConcurrentHashSetTest.cs diff --git a/itext.tests/itext.commons.tests/itext/commons/actions/EventManagerTest.cs b/itext.tests/itext.commons.tests/itext/commons/actions/EventManagerTest.cs index e0a8f7121b..145249360b 100644 --- a/itext.tests/itext.commons.tests/itext/commons/actions/EventManagerTest.cs +++ b/itext.tests/itext.commons.tests/itext/commons/actions/EventManagerTest.cs @@ -56,12 +56,12 @@ public virtual void ThrowSomeExceptionsTest() { eventManager.OnEvent(new ITextTestEvent(sequenceId, null, "test-event", ProductNameConstant.ITEXT_CORE)); } catch (AggregatedException e) { - NUnit.Framework.Assert.AreEqual("Error during event processing:\n" + "0) ThrowArithmeticExpHandler\n" + "1) ThrowIllegalArgumentExpHandler\n" - , e.Message); IList aggregatedExceptions = e.GetAggregatedExceptions(); NUnit.Framework.Assert.AreEqual(2, aggregatedExceptions.Count); - NUnit.Framework.Assert.AreEqual("ThrowArithmeticExpHandler", aggregatedExceptions[0].Message); - NUnit.Framework.Assert.AreEqual("ThrowIllegalArgumentExpHandler", aggregatedExceptions[1].Message); + NUnit.Framework.Assert.AreEqual("ThrowArithmeticExpHandler", FindHandler(aggregatedExceptions, typeof(ArithmeticException + )).Message); + NUnit.Framework.Assert.AreEqual("ThrowIllegalArgumentExpHandler", FindHandler(aggregatedExceptions, typeof( + ArgumentException)).Message); } eventManager.Unregister(handler1); eventManager.Unregister(handler2); @@ -106,6 +106,15 @@ public virtual void TurningOffAgplTest() { NUnit.Framework.Assert.IsTrue(underAgplProductProcessorFactory1 is UnderAgplProductProcessorFactory); } + private Exception FindHandler(IList events, Type eventClass) { + foreach (Exception @event in events) { + if (eventClass.IsInstanceOfType(@event)) { + return @event; + } + } + return null; + } + private class ThrowArithmeticExpHandler : IEventHandler { public virtual void OnEvent(IEvent @event) { throw new ArithmeticException("ThrowArithmeticExpHandler"); diff --git a/itext.tests/itext.commons.tests/itext/commons/datastructures/ConcurrentHashSetTest.cs b/itext.tests/itext.commons.tests/itext/commons/datastructures/ConcurrentHashSetTest.cs new file mode 100644 index 0000000000..23f044d151 --- /dev/null +++ b/itext.tests/itext.commons.tests/itext/commons/datastructures/ConcurrentHashSetTest.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using iText.Commons.Exceptions; +using iText.Commons.Utils; +using iText.Test; + +namespace iText.Commons.Datastructures { + [NUnit.Framework.Category("UnitTest")] + public class ConcurrentHashSetTest : ExtendedITextTest { + [NUnit.Framework.Test] + public virtual void SizeTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + set.Add("1"); + set.Add("2"); + NUnit.Framework.Assert.AreEqual(2, set.Count); + } + + [NUnit.Framework.Test] + public virtual void ContainsKeyTrueTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + set.Add("1"); + set.Add("2"); + set.Add("3"); + NUnit.Framework.Assert.IsTrue(set.Contains("1")); + NUnit.Framework.Assert.IsTrue(set.Contains("2")); + NUnit.Framework.Assert.IsTrue(set.Contains("3")); + } + + [NUnit.Framework.Test] + public virtual void ContainsKeyFalseTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + set.Add("1"); + NUnit.Framework.Assert.IsFalse(set.Contains("5")); + } + + [NUnit.Framework.Test] + public virtual void ClearTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + set.Add("1"); + set.Clear(); + NUnit.Framework.Assert.IsFalse(set.Contains("1")); + } + + [NUnit.Framework.Test] + public virtual void AddTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + set.Add("1"); + set.Add("1"); + NUnit.Framework.Assert.AreEqual(1, set.Count); + } + + [NUnit.Framework.Test] + public virtual void RemoveTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + set.Add("1"); + set.Add("2"); + set.Remove("1"); + NUnit.Framework.Assert.IsFalse(set.Contains("1")); + } + + [NUnit.Framework.Test] + public virtual void ForEachTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + ICollection anotherSet = new HashSet(); + set.Add("1"); + set.Add("2"); + set.Add("3"); + set.ForEach((str) => { + anotherSet.Add(str); + } + ); + NUnit.Framework.Assert.AreEqual(3, anotherSet.Count); + } + + [NUnit.Framework.Test] + public virtual void EqualsTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + set.Add("1"); + HashSet anotherSet = new HashSet(); + NUnit.Framework.Assert.IsFalse(set.Equals(anotherSet)); + } + + [NUnit.Framework.Test] + public virtual void AddAllTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + HashSet anotherSet = new HashSet(); + anotherSet.Add("1"); + anotherSet.Add("2"); + set.AddAll(anotherSet); + NUnit.Framework.Assert.AreEqual(2, set.Count); + } + + [NUnit.Framework.Test] + public virtual void RemoveAllTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + HashSet anotherSet = new HashSet(); + Exception e = NUnit.Framework.Assert.Catch(typeof(NotSupportedException), () => set.RemoveAll(anotherSet)); + NUnit.Framework.Assert.AreEqual(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION, e.Message); + } + + [NUnit.Framework.Test] + public virtual void RetainAllTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + HashSet anotherSet = new HashSet(); + Exception e = NUnit.Framework.Assert.Catch(typeof(NotSupportedException), () => set.RetainAll(anotherSet)); + NUnit.Framework.Assert.AreEqual(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION, e.Message); + } + + [NUnit.Framework.Test] + public virtual void ContainsAllTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + HashSet anotherSet = new HashSet(); + Exception e = NUnit.Framework.Assert.Catch(typeof(NotSupportedException), () => set.ContainsAll(anotherSet + )); + NUnit.Framework.Assert.AreEqual(CommonsExceptionMessageConstant.UNSUPPORTED_OPERATION, e.Message); + } + + [NUnit.Framework.Test] + public virtual void HashCodeTest() { + ConcurrentHashSet set = new ConcurrentHashSet(); + set.Add("1"); + HashSet anotherSet = new HashSet(); + anotherSet.Add("2"); + NUnit.Framework.Assert.AreNotEqual(set.GetHashCode(), JavaUtil.SetHashCode(anotherSet)); + } + } +} diff --git a/itext/itext.commons/itext/commons/actions/EventManager.cs b/itext/itext.commons/itext/commons/actions/EventManager.cs index c5af788e8a..a4e6a29cd8 100644 --- a/itext/itext.commons/itext/commons/actions/EventManager.cs +++ b/itext/itext.commons/itext/commons/actions/EventManager.cs @@ -23,8 +23,8 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Collections.Generic; using iText.Commons.Actions.Processors; +using iText.Commons.Datastructures; using iText.Commons.Exceptions; -using iText.Commons.Utils; namespace iText.Commons.Actions { /// Entry point for event handling mechanism. @@ -37,7 +37,7 @@ public sealed class EventManager { private static readonly iText.Commons.Actions.EventManager INSTANCE = new iText.Commons.Actions.EventManager (); - private readonly ICollection handlers = new LinkedHashSet(); + private readonly ConcurrentHashSet handlers = new ConcurrentHashSet(); private EventManager() { handlers.Add(ProductEventHandler.INSTANCE); @@ -63,7 +63,7 @@ public static void AcknowledgeAgplUsageDisableWarningMessage() { /// to handle public void OnEvent(IEvent @event) { IList caughtExceptions = new List(); - foreach (IEventHandler handler in handlers) { + handlers.ForEach((handler) => { try { handler.OnEvent(@event); } @@ -71,6 +71,7 @@ public void OnEvent(IEvent @event) { caughtExceptions.Add(ex); } } + ); if (@event is AbstractITextConfigurationEvent) { try { AbstractITextConfigurationEvent itce = (AbstractITextConfigurationEvent)@event; diff --git a/itext/itext.commons/itext/commons/exceptions/CommonsExceptionMessageConstant.cs b/itext/itext.commons/itext/commons/exceptions/CommonsExceptionMessageConstant.cs index 45ae9c4dd5..ec1ca9327d 100644 --- a/itext/itext.commons/itext/commons/exceptions/CommonsExceptionMessageConstant.cs +++ b/itext/itext.commons/itext/commons/exceptions/CommonsExceptionMessageConstant.cs @@ -69,6 +69,8 @@ public sealed class CommonsExceptionMessageConstant { public const String ZIP_ENTRY_NOT_FOUND = "Zip entry not found for name: {0}"; + public const String UNSUPPORTED_OPERATION = "This operation is not supported."; + private CommonsExceptionMessageConstant() { } // Empty constructor. diff --git a/port-hash b/port-hash index 8f89639b35..b34df2794a 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -a08fbb5623b5fba0e06418d94601e1054c5f1b0a +0a8638bed4421f046031ae84ecde685b3a8b34c1 From b13f28207cf95969b53504e69b54152db9e5e8fc Mon Sep 17 00:00:00 2001 From: iText Software Date: Wed, 11 Dec 2024 17:24:35 +0000 Subject: [PATCH 3/3] Add missing copyright headers Autoported commit. Original commit hash: [7b8cfea5e] Manual files: commons/src/main/java/com/itextpdf/commons/datastructures/ConcurrentHashSet.java --- .../datastructures/ConcurrentHashSetTest.cs | 22 +++++++++++++++++++ port-hash | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/itext.tests/itext.commons.tests/itext/commons/datastructures/ConcurrentHashSetTest.cs b/itext.tests/itext.commons.tests/itext/commons/datastructures/ConcurrentHashSetTest.cs index 23f044d151..d4b2488980 100644 --- a/itext.tests/itext.commons.tests/itext/commons/datastructures/ConcurrentHashSetTest.cs +++ b/itext.tests/itext.commons.tests/itext/commons/datastructures/ConcurrentHashSetTest.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using System.Collections.Generic; using iText.Commons.Exceptions; diff --git a/port-hash b/port-hash index b34df2794a..53b510ebd9 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -0a8638bed4421f046031ae84ecde685b3a8b34c1 +7b8cfea5e5d0c751a7e66d06e7da481e33868d68