| | 1 | | using System.Reflection; |
| | 2 | | using Spdx3.Exceptions; |
| | 3 | | using Spdx3.Model; |
| | 4 | | using Spdx3.Model.Core.Classes; |
| | 5 | | using Spdx3.Model.Core.Enums; |
| | 6 | |
|
| | 7 | | namespace Spdx3.Utility; |
| | 8 | |
|
| | 9 | | /// <summary> |
| | 10 | | /// A class that tracks objects created and helps make ID's for new objets. |
| | 11 | | /// </summary> |
| | 12 | | public class Catalog |
| | 13 | | { |
| | 14 | | private long _idCounter; |
| | 15 | |
|
| 220 | 16 | | public Catalog(int startCounterAt) |
| | 17 | | { |
| 220 | 18 | | _idCounter = startCounterAt; |
| 220 | 19 | | } |
| | 20 | |
|
| 19 | 21 | | public Catalog() |
| | 22 | | { |
| 19 | 23 | | _idCounter = DateTime.Now.Ticks - (new DateTime(2025,1,1,0,0,0) ).Ticks; |
| 19 | 24 | | } |
| | 25 | |
|
| | 26 | |
|
| | 27 | | // A running list of all the objects created |
| 1479 | 28 | | public IDictionary<Uri, BaseModelClass> Items { get; } = new Dictionary<Uri, BaseModelClass>(); |
| | 29 | |
|
| | 30 | | /// <summary> |
| | 31 | | /// There's no semantic meaning to an SPDX ID, and no real correspondence to real life objects. |
| | 32 | | /// It's just an element identifier used within the document, so things can reference each other. |
| | 33 | | /// </summary> |
| | 34 | | /// <param name="type">The type of object to create an ID for</param> |
| | 35 | | /// <returns>A URN that can be used as a node identifier (spdxId)</returns> |
| | 36 | | public Uri NewId(Type type) |
| | 37 | | { |
| 1054 | 38 | | return new Uri($"urn:{type.Name}:{GetShortUid()}"); |
| | 39 | | } |
| | 40 | |
|
| | 41 | | /// <summary> |
| | 42 | | /// Get the one SpdxDocument object from the catalog |
| | 43 | | /// </summary> |
| | 44 | | /// <returns>The one SpdxDocument</returns> |
| | 45 | | /// <exception cref="Spdx3Exception">If not exactly one SpdxDocument could be found.</exception> |
| | 46 | | public SpdxDocument GetSpdxDocument() |
| | 47 | | { |
| 2 | 48 | | var spdxDocs = GetItems<SpdxDocument>(); |
| | 49 | |
|
| 2 | 50 | | if (spdxDocs.Count != 1) |
| | 51 | | { |
| 0 | 52 | | throw new Spdx3Exception($"Expected exactly one SpdxDocument, but got {spdxDocs.Count}."); |
| | 53 | | } |
| | 54 | |
|
| 2 | 55 | | var result = spdxDocs.First(); |
| 2 | 56 | | return result; |
| | 57 | | } |
| | 58 | |
|
| | 59 | | /// <summary> |
| | 60 | | /// Reconstruct a model object graph from the flat list of items in the catalog |
| | 61 | | /// </summary> |
| | 62 | | /// <returns>The SpdxDocument that is the top-level element in the document</returns> |
| | 63 | | /// <exception cref="Spdx3SerializationException">If there is not exactly one SpdxDocument element in the catalog</e |
| | 64 | | public SpdxDocument GetModel() |
| | 65 | | { |
| 2 | 66 | | ReplacePlaceHoldersWithRealObjects(); |
| 2 | 67 | | return AssembleSpdxDocument(); |
| | 68 | | } |
| | 69 | |
|
| | 70 | | private string GetShortUid() |
| | 71 | | { |
| | 72 | | // add 13 each time so the numbers look more different from each other and don't look like an incrementing count |
| 1054 | 73 | | return (_idCounter += 13).ToString("x"); |
| | 74 | | } |
| | 75 | |
|
| | 76 | |
|
| | 77 | | /// <summary> |
| | 78 | | /// Take all the rehydrated items in the catalog, and assemble them into an SpdxDocument object |
| | 79 | | /// </summary> |
| | 80 | | /// <returns>The assembled SpdxDocument from the catalog</returns> |
| | 81 | | private SpdxDocument AssembleSpdxDocument() |
| | 82 | | { |
| 2 | 83 | | var result = GetSpdxDocument(); |
| | 84 | |
|
| 114 | 85 | | foreach (var baseModelClass in Items.Values.ToList()) |
| | 86 | | { |
| 55 | 87 | | if (baseModelClass is Element e) |
| | 88 | | { |
| 45 | 89 | | result.Element.Add(e); |
| | 90 | | } |
| | 91 | | } |
| | 92 | |
|
| 2 | 93 | | return result; |
| | 94 | | } |
| | 95 | |
|
| | 96 | | /// <summary> |
| | 97 | | /// Replace all the placeholders in the Catalog items with references to real objects |
| | 98 | | /// </summary> |
| | 99 | | private void ReplacePlaceHoldersWithRealObjects() |
| | 100 | | { |
| 114 | 101 | | foreach (var item in Items.Values.ToList()) |
| | 102 | | { |
| | 103 | | // Get the properties that are Spdx Model Class types and are not null |
| 55 | 104 | | var props = item.GetType().GetProperties() |
| 925 | 105 | | .Where(p => p.GetValue(item) != null && p.GetValue(item) is BaseModelClass); |
| | 106 | |
|
| 220 | 107 | | foreach (var prop in props) |
| | 108 | | { |
| 55 | 109 | | RehydratePlaceHolderWithRealItem(prop, item); |
| | 110 | | } |
| | 111 | | } |
| 2 | 112 | | } |
| | 113 | |
|
| | 114 | | /// <summary> |
| | 115 | | /// Rehydrate a specific property (which has a placeholder value) on an object with the real object from the cat |
| | 116 | | /// </summary> |
| | 117 | | /// <param name="prop">The property that currently has a placeholder that needs replacing</param> |
| | 118 | | /// <param name="itemWithProperty">The item that has the placeholder property</param> |
| | 119 | | /// <exception cref="Spdx3Exception">If </exception> |
| | 120 | | private void RehydratePlaceHolderWithRealItem(PropertyInfo prop, BaseModelClass itemWithProperty) |
| | 121 | | { |
| 55 | 122 | | var isSpdxClass = prop.PropertyType.IsAssignableTo(typeof(BaseModelClass)); |
| | 123 | |
|
| 55 | 124 | | if (!isSpdxClass) |
| | 125 | | { |
| 0 | 126 | | return; |
| | 127 | | } |
| | 128 | |
|
| 55 | 129 | | var placeHolder = prop.GetValue(itemWithProperty) as BaseModelClass; |
| | 130 | | #pragma warning disable CS8602 // Dereference of a possibly null reference. |
| 55 | 131 | | if (Items.TryGetValue(placeHolder.SpdxId, out var value)) |
| | 132 | | #pragma warning restore CS8602 // Dereference of a possibly null reference. |
| | 133 | | { |
| 55 | 134 | | prop.SetValue(itemWithProperty, value); |
| | 135 | | } |
| | 136 | |
|
| 55 | 137 | | } |
| | 138 | |
|
| | 139 | | /// <summary> |
| | 140 | | /// Get all the items in the catalog of type T |
| | 141 | | /// </summary> |
| | 142 | | /// <typeparam name="T">The type of items in the catalog you want</typeparam> |
| | 143 | | /// <returns>The items in the catalog of type T as a List</returns> |
| | 144 | | public List<T> GetItems<T>() |
| | 145 | | { |
| 893 | 146 | | return Items.Values.ToList().Where(x => x.GetType() == typeof(T)).Cast<T>().ToList(); |
| | 147 | | } |
| | 148 | |
|
| | 149 | | public List<Relationship> GetRelationshipsFromTo<TF, TT>() |
| | 150 | | { |
| 4 | 151 | | var relationships = GetItems<Relationship>(); |
| 4 | 152 | | var result = new List<Relationship>(); |
| | 153 | |
|
| 52 | 154 | | foreach (var r in relationships.Where(r => r.From.GetType().IsAssignableTo(typeof(TF)))) |
| | 155 | | { |
| 22 | 156 | | if (r.To.Any(t => t.GetType().IsAssignableFrom(typeof(TT)))) |
| | 157 | | { |
| 6 | 158 | | result.Add(r); |
| | 159 | | } |
| | 160 | | } |
| | 161 | |
|
| 4 | 162 | | return result; |
| | 163 | | } |
| | 164 | |
|
| | 165 | | public List<Relationship> GetRelationshipsFromTo(Element fromElement, Element toElement) |
| | 166 | | { |
| 9 | 167 | | return GetItems<Relationship>() |
| 76 | 168 | | .Where(r => r.From.SpdxId == fromElement.SpdxId && r.To.Any(t => t.SpdxId == toElement.SpdxId)).ToList(); |
| | 169 | | } |
| | 170 | |
|
| | 171 | | public List<Relationship> GetRelationshipsFrom(Element fromElement) |
| | 172 | | { |
| 42 | 173 | | return GetItems<Relationship>().Where(r => r.From.SpdxId == fromElement.SpdxId).ToList(); |
| | 174 | | } |
| | 175 | |
|
| | 176 | | public List<Relationship> GetRelationshipsFrom<T>() |
| | 177 | | { |
| 28 | 178 | | return GetItems<Relationship>().Where(r => r.From.GetType().IsAssignableTo(typeof(T))).ToList(); |
| | 179 | | } |
| | 180 | |
|
| | 181 | | public List<Relationship> GetRelationshipsTo(Element toElement) |
| | 182 | | { |
| 88 | 183 | | return GetItems<Relationship>().Where(r => r.To.Select(x => x.SpdxId).Contains(toElement.SpdxId)).ToList(); |
| | 184 | | } |
| | 185 | |
|
| | 186 | | public List<Relationship> GetRelationshipsTo<T>() |
| | 187 | | { |
| 58 | 188 | | return GetItems<Relationship>().Where(r => r.To.Any(x => x.GetType() == typeof(T))).ToList(); |
| | 189 | | } |
| | 190 | |
|
| | 191 | | public List<Relationship> GetRelationshipsOfType(RelationshipType relType) |
| | 192 | | { |
| 12 | 193 | | return GetItems<Relationship>().Where(r => r.RelationshipType == relType).ToList(); |
| | 194 | | } |
| | 195 | | } |