Skip to content

Commit 4214bfe

Browse files
[Add] AnnotatingElementComparer with supporting GuidSequenceEquality and StringSequenceEquality helpers
1 parent ea2f6a8 commit 4214bfe

File tree

3 files changed

+336
-0
lines changed

3 files changed

+336
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// <copyright file="GuidSequenceEquality.cs" company="Starion Group S.A.">
3+
//
4+
// Copyright 2022-2026 Starion Group S.A.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// </copyright>
19+
// ------------------------------------------------------------------------------------------------
20+
21+
namespace SysML2.NET.Extensions.Comparers
22+
{
23+
using System;
24+
using System.Collections.Generic;
25+
26+
/// <summary>
27+
/// Provides utility methods for comparing sequences of <see cref="Guid"/> values
28+
/// according to ordered and unordered equality semantics.
29+
/// </summary>
30+
public static class GuidSequenceEquality
31+
{
32+
/// <summary>
33+
/// Determines whether two sequences of <see cref="Guid"/> values are equal
34+
/// using an ordered (positional) comparison.
35+
/// </summary>
36+
/// <param name="a">
37+
/// The first sequence to compare. The order of elements is significant.
38+
/// </param>
39+
/// <param name="b">
40+
/// The second sequence to compare. The order of elements is significant.
41+
/// </param>
42+
/// <returns>
43+
/// <c>true</c> if both sequences contain the same number of elements and each
44+
/// element in <paramref name="a"/> is equal to the corresponding element in
45+
/// <paramref name="b"/> at the same position; otherwise, <c>false</c>.
46+
/// </returns>
47+
/// <remarks>
48+
/// This method performs a fast, allocation-free comparison and is suitable
49+
/// for properties with <c>isOrdered = true</c> semantics in the metamodel.
50+
/// </remarks>
51+
public static bool OrderedEqual(IReadOnlyList<Guid> a, IReadOnlyList<Guid> b)
52+
{
53+
if (ReferenceEquals(a, b)) return true;
54+
if (a is null || b is null) return false;
55+
56+
var count = a.Count;
57+
if (count != b.Count) return false;
58+
59+
for (var i = 0; i < count; i++)
60+
{
61+
if (a[i] != b[i]) return false;
62+
}
63+
64+
return true;
65+
}
66+
67+
/// <summary>
68+
/// Determines whether two sequences of <see cref="Guid"/> values are equal
69+
/// regardless of element ordering.
70+
/// </summary>
71+
/// <param name="a">
72+
/// The first sequence to compare. Element order is ignored.
73+
/// </param>
74+
/// <param name="b">
75+
/// The second sequence to compare. Element order is ignored.
76+
/// </param>
77+
/// <returns>
78+
/// <c>true</c> if both sequences contain the same elements with the same
79+
/// multiplicities, independent of order; otherwise, <c>false</c>.
80+
/// </returns>
81+
/// <remarks>
82+
/// This method uses a hash-based comparison and correctly handles duplicate
83+
/// values. It is suitable for properties with <c>isOrdered = false</c>
84+
/// semantics in the metamodel.
85+
/// </remarks>
86+
public static bool UnorderedEqual(IReadOnlyList<Guid> a, IReadOnlyList<Guid> b)
87+
{
88+
if (ReferenceEquals(a, b)) return true;
89+
if (a is null || b is null) return false;
90+
91+
var count = a.Count;
92+
if (count != b.Count) return false;
93+
94+
var set = new HashSet<Guid>(a);
95+
for (var i = 0; i < count; i++)
96+
{
97+
if (!set.Remove(b[i]))
98+
{
99+
return false;
100+
}
101+
}
102+
103+
return set.Count == 0;
104+
}
105+
}
106+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// <copyright file="StringSequenceEquality.cs" company="Starion Group S.A.">
3+
//
4+
// Copyright 2022-2025 Starion Group S.A.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// </copyright>
19+
// ------------------------------------------------------------------------------------------------
20+
21+
namespace SysML2.NET.Extensions.Comparers
22+
{
23+
using System;
24+
using System.Collections.Generic;
25+
26+
/// <summary>
27+
/// Provides utility methods for comparing sequences of <see cref="string"/> values
28+
/// using ordered and unordered equality semantics.
29+
/// </summary>
30+
/// <remarks>
31+
/// All comparisons performed by this class use <em>ordinal</em> string semantics
32+
/// by default (<see cref="StringComparer.Ordinal"/>), ensuring deterministic,
33+
/// culture-independent behavior suitable for identifiers, names, and other
34+
/// model-level string values.
35+
/// </remarks>
36+
public class StringSequenceEquality
37+
{
38+
/// <summary>
39+
/// Determines whether two string sequences are equal using an ordered
40+
/// (positional) comparison with ordinal semantics.
41+
/// </summary>
42+
public static bool OrderedEqual(IReadOnlyList<string> a, IReadOnlyList<string> b, StringComparer comparer = null)
43+
{
44+
comparer ??= StringComparer.Ordinal;
45+
46+
if (ReferenceEquals(a, b)) return true;
47+
if (a is null || b is null) return false;
48+
49+
var count = a.Count;
50+
if (count != b.Count) return false;
51+
52+
for (var i = 0; i < count; i++)
53+
{
54+
if (!comparer.Equals(a[i], b[i]))
55+
{
56+
return false;
57+
}
58+
}
59+
60+
return true;
61+
}
62+
63+
/// <summary>
64+
/// Determines whether two string sequences are equal regardless of order,
65+
/// using ordinal semantics.
66+
/// </summary>
67+
public static bool UnorderedEqual(IReadOnlyList<string> a, IReadOnlyList<string> b, StringComparer comparer = null)
68+
{
69+
comparer ??= StringComparer.Ordinal;
70+
71+
if (ReferenceEquals(a, b)) return true;
72+
if (a is null || b is null) return false;
73+
74+
var count = a.Count;
75+
if (count != b.Count) return false;
76+
77+
var set = new Dictionary<string, int>(count, comparer);
78+
79+
for (var i = 0; i < count; i++)
80+
{
81+
var value = a[i];
82+
if (set.TryGetValue(value, out var current))
83+
{
84+
set[value] = current + 1;
85+
}
86+
else
87+
{
88+
set[value] = 1;
89+
}
90+
}
91+
92+
for (var i = 0; i < count; i++)
93+
{
94+
var value = b[i];
95+
if (!set.TryGetValue(value, out var current))
96+
{
97+
return false;
98+
}
99+
100+
if (current == 1)
101+
{
102+
set.Remove(value);
103+
}
104+
else
105+
{
106+
set[value] = current - 1;
107+
}
108+
}
109+
110+
return set.Count == 0;
111+
}
112+
}
113+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// <copyright file="AnnotatingElementComparer.cs" company="Starion Group S.A.">
3+
//
4+
// Copyright 2022-2026 Starion Group S.A.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// </copyright>
19+
// ------------------------------------------------------------------------------------------------
20+
21+
namespace SysML2.NET.Extensions.Core.DTO.Comparers
22+
{
23+
using System;
24+
using System.CodeDom.Compiler;
25+
using System.Collections.Generic;
26+
27+
using SysML2.NET.Core.DTO.Root.Annotations;
28+
using SysML2.NET.Extensions.Comparers;
29+
30+
/// <summary>
31+
/// Provides an equality comparison for <see cref="IAnnotatingElement"/> instances
32+
/// based on their semantic content rather than object reference identity.
33+
/// </summary>
34+
/// <remarks>
35+
/// This comparer is intended for use in deserialization, testing, and model
36+
/// validation scenarios where two <see cref="IAnnotatingElement"/> instances
37+
/// originating from different sources (e.g. JSON and MessagePack) must be
38+
/// considered equal if they represent the same SysML v2 model element.
39+
/// </remarks>
40+
[GeneratedCode("SysML2.NET", "latest")]
41+
public sealed class AnnotatingElementComparer : IEqualityComparer<IAnnotatingElement>
42+
{
43+
/// <summary>
44+
/// The <see cref="StringComparer"/> used for all string comparisons performed
45+
/// by this comparer.
46+
/// </summary>
47+
/// <remarks>
48+
/// <see cref="StringComparer.Ordinal"/> is used to ensure culture-independent,
49+
/// deterministic comparison semantics suitable for identifiers and
50+
/// model-level string values.
51+
/// </remarks>
52+
private static readonly StringComparer OrdinalStringComparer = StringComparer.Ordinal;
53+
54+
/// <summary>
55+
/// Determines whether two <see cref="IAnnotatingElement"/> instances are equal.
56+
/// </summary>
57+
/// <param name="x">
58+
/// The first <see cref="IAnnotatingElement"/> to compare.
59+
/// </param>
60+
/// <param name="y">
61+
/// The second <see cref="IAnnotatingElement"/> to compare.
62+
/// </param>
63+
/// <returns>
64+
/// <c>true</c> if both instances represent the same <see cref="IAnnotatingElement"/>
65+
/// according to their semantic content; otherwise, <c>false</c>.
66+
/// </returns>
67+
/// <remarks>
68+
/// This method performs a deep, deterministic comparison of all relevant
69+
/// properties, ignoring object reference identity and any transient or
70+
/// derived state.
71+
/// </remarks>
72+
public bool Equals(IAnnotatingElement x, IAnnotatingElement y)
73+
{
74+
if (ReferenceEquals(x, y)) return true;
75+
76+
if (x is null || y is null) return false;
77+
78+
if (x.Id != y.Id) return false;
79+
80+
if (!StringSequenceEquality.OrderedEqual(x.AliasIds, y.AliasIds)) return false;
81+
82+
if (!OrdinalStringComparer.Equals(x.DeclaredName, y.DeclaredName)) return false;
83+
84+
if (!OrdinalStringComparer.Equals(x.DeclaredShortName, y.DeclaredShortName)) return false;
85+
86+
if (!OrdinalStringComparer.Equals(x.ElementId, y.ElementId)) return false;
87+
88+
if (x.IsImpliedIncluded != y.IsImpliedIncluded) return false;
89+
90+
if (!GuidSequenceEquality.OrderedEqual(x.OwnedRelationship, y.OwnedRelationship)) return false;
91+
92+
if (x.OwningRelationship != y.OwningRelationship) return false;
93+
94+
return true;
95+
}
96+
97+
/// <summary>
98+
/// Returns a hash code for the specified <see cref="IAnnotatingElement"/>.
99+
/// </summary>
100+
/// <param name="obj">
101+
/// The <see cref="IAnnotatingElement"/> for which a hash code is to be returned.
102+
/// </param>
103+
/// <returns>
104+
/// A hash code based on the stable identity of the element.
105+
/// </returns>
106+
/// <remarks>
107+
/// The hash code is intentionally derived from the element identity only,
108+
/// ensuring stability and compatibility with collection-based equality
109+
/// checks
110+
/// </remarks>
111+
public int GetHashCode(IAnnotatingElement obj)
112+
{
113+
return HashCode.Combine(typeof(IAnnotatingElement), obj.Id);
114+
}
115+
}
116+
}
117+

0 commit comments

Comments
 (0)