Skip to content

Commit

Permalink
improve merge performance
Browse files Browse the repository at this point in the history
Signed-off-by: andreas hilti <andreas.hilti@bluewin.ch>
  • Loading branch information
andreas-hilti committed May 25, 2024
1 parent 7a0682d commit 05088c3
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 4 deletions.
13 changes: 13 additions & 0 deletions src/CycloneDX.Core/Json/Serializer.Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,18 @@ internal static string Serialize(Models.Vulnerabilities.Vulnerability vulnerabil
Contract.Requires(vulnerability != null);
return JsonSerializer.Serialize(vulnerability, _options);
}

internal static string Serialize(Models.Composition composition)
{
Contract.Requires(composition != null);
return JsonSerializer.Serialize(composition, _options);
}

internal static string Serialize(Models.ExternalReference externalReference)
{
Contract.Requires(externalReference != null);
return JsonSerializer.Serialize(externalReference, _options);
}

}
}
12 changes: 11 additions & 1 deletion src/CycloneDX.Core/Models/Composition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
namespace CycloneDX.Models
{
[ProtoContract]
public class Composition : IXmlSerializable
public class Composition : IXmlSerializable, IEquatable<Composition>
{
[ProtoContract]
public enum AggregateType
Expand Down Expand Up @@ -194,5 +194,15 @@ public void WriteXml(System.Xml.XmlWriter writer) {
writer.WriteEndElement();
}
}

public bool Equals(Composition obj)
{
return CycloneDX.Json.Serializer.Serialize(this) == CycloneDX.Json.Serializer.Serialize(obj);
}

public override int GetHashCode()
{
return CycloneDX.Json.Serializer.Serialize(this).GetHashCode();
}
}
}
14 changes: 13 additions & 1 deletion src/CycloneDX.Core/Models/ExternalReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) OWASP Foundation. All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
using ProtoBuf;

namespace CycloneDX.Models
{
[SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
[ProtoContract]
public class ExternalReference
public class ExternalReference : IEquatable<ExternalReference>
{
[ProtoContract]
public enum ExternalReferenceType
Expand Down Expand Up @@ -125,5 +127,15 @@ public enum ExternalReferenceType
[ProtoMember(4)]
public List<Hash> Hashes { get; set; }
public bool ShouldSerializeHashes() { return Hashes?.Count > 0; }

public bool Equals(ExternalReference obj)
{
return CycloneDX.Json.Serializer.Serialize(this) == CycloneDX.Json.Serializer.Serialize(obj);
}

public override int GetHashCode()
{
return CycloneDX.Json.Serializer.Serialize(this).GetHashCode();
}
}
}
28 changes: 26 additions & 2 deletions src/CycloneDX.Utils/Merge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,44 @@

namespace CycloneDX.Utils
{
class ListMergeHelper<T>
class ListMergeHelper<T> where T : IEquatable<T>
{
public List<T> Merge(List<T> list1, List<T> list2)
{
if (list1 is null) return list2;
if (list2 is null) return list1;

var result = new List<T>(list1);
// We want to avoid the costly computation of the hashes if possible.
// Therefore, we use a nullable type.
var resultHashes = new List<int?>(list1.Count);
for (int i = 0; i < list1.Count; i++)
{
resultHashes.Add(null);
}

foreach (var item in list2)
{
if (!(result.Contains(item)))
int hash = item.GetHashCode();
bool found = false;
for (int i = 0; i < result.Count; i++)
{
var resultItem = result[i];
if (resultHashes[i] == null)
{
resultHashes[i] = resultItem.GetHashCode();
}
int resultHash = resultHashes[i].Value;
if (hash == resultHash && item.Equals(resultItem))
{
found = true;
break;
}
}
if (!found)
{
result.Add(item);
resultHashes.Add(hash);
}
}

Expand Down
25 changes: 25 additions & 0 deletions tests/CycloneDX.Utils.Tests/MergeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,31 @@ public void FlatMergeComponentsTest()
Snapshot.Match(result);
}

[Fact]
public void FlatMergeDuplicatedComponentsTest()
{
var sboms = new List<Bom>();
for (int i = 0; i < 3; i++)
{
var bom = new Bom
{
Components = new List<Component>
{
new Component
{
Name = "Component1",
Version = "1"
}
}
};
sboms.Add(bom);
}
var result = CycloneDXUtils.FlatMerge(sboms);

Assert.Single(result.Components);
}


[Fact]
public void FlatMergeVulnerabilitiesTest()
{
Expand Down

0 comments on commit 05088c3

Please sign in to comment.