Files
speckle-sharp-sdk/src/Speckle.Core/Models/CommitObjectBuilder.cs
T
Adam Hathcock 787ad92ff2 Remove extra usings (#55)
* Remove extra usings

* readd serilog
2024-07-30 11:07:52 +01:00

185 lines
6.6 KiB
C#

using System.Collections;
using System.Diagnostics.CodeAnalysis;
using Speckle.Core.Logging;
using Speckle.Core.Models.Collections;
using Speckle.Core.Models.Extensions;
namespace Speckle.Core.Models;
/// <summary>
/// Abstract Builder class for a root commit <see cref="Base"/> object.
/// </summary>
/// <typeparam name="TNativeObjectData">The native object data type needed as input for building <see cref="_nestingInstructions"/></typeparam>
/// <remarks>
/// It is designed to be inherited by a host app specific implementation,
/// to give connectors flexibility in constructing their objects.
/// Inheritors should also create some function to add
/// </remarks>
[SuppressMessage(
"Naming",
"CA1708:Identifiers should differ by more than case",
Justification = "Class contains obsolete members that are kept for backwards compatiblity"
)]
public abstract class CommitObjectBuilder<TNativeObjectData>
{
/// <summary>Special appId symbol for the root object</summary>
protected const string ROOT = "__Root";
private const string ELEMENTS = nameof(Collection.elements);
/// <summary>app id -> base</summary>
protected IDictionary<string, Base> Converted { get; }
/// <summary>Base -> NestingInstructions ordered by priority</summary>
private readonly Dictionary<Base, IList<NestingInstructions>> _nestingInstructions = new();
protected CommitObjectBuilder()
{
Converted = new Dictionary<string, Base>();
}
/// <summary>
/// Given the parameters, builds connector specific <see cref="_nestingInstructions"/>
/// to be applied when <see cref="BuildCommitObject"/> is called.
/// </summary>
/// <param name="conversionResult"></param>
/// <param name="nativeElement"></param>
public abstract void IncludeObject(Base conversionResult, TNativeObjectData nativeElement);
/// <summary>
/// Iterates through the converted objects applying
/// </summary>
/// <remarks>
/// Can be overriden to adjust exactly which objects get automatically applied,
/// or to inject additional items into the <see cref="Converted"/> dict that should not be automatically applied.
/// </remarks>
/// <param name="rootCommitObject"></param>
public virtual void BuildCommitObject(Base rootCommitObject)
{
ApplyRelationships(Converted.Values, rootCommitObject);
}
protected void SetRelationship(Base conversionResult, NestingInstructions nestingInstructions)
{
SetRelationship(conversionResult, new List<NestingInstructions> { nestingInstructions });
}
/// <summary>
/// Sets information on how a given object should be nested in the commit tree.
/// <paramref name="nestingInstructionsList"/> encodes the order in which we should try and nest the given <paramref name="conversionResult"/>
/// when <see cref="BuildCommitObject"/> is called
/// </summary>
/// <param name="conversionResult">The object to be nested</param>
/// <param name="nestingInstructionsList">Information about how the object ideally should be nested, in order of priority</param>
protected void SetRelationship(Base conversionResult, IList<NestingInstructions> nestingInstructionsList)
{
string? appId = conversionResult.applicationId;
if (appId != null)
{
Converted[appId] = conversionResult;
}
_nestingInstructions[conversionResult] = nestingInstructionsList;
}
/// <summary>
/// For each object in <paramref name="toAdd"/>
/// <inheritdoc cref="ApplyRelationship"/>
/// </summary>
/// <param name="toAdd"></param>
/// <param name="rootCommitObject"></param>
protected void ApplyRelationships(IEnumerable<Base> toAdd, Base rootCommitObject)
{
foreach (Base c in toAdd)
{
try
{
ApplyRelationship(c, rootCommitObject);
}
catch (Exception ex) when (!ex.IsFatal())
{
// This should never happen, we should be ensuring that at least one of the parents is valid.
SpeckleLog.Logger.Fatal(ex, "Failed to add object {speckleType} to commit object", c?.GetType());
}
}
}
/// <summary>
/// Will attempt to find and nest the <paramref name="current"/> object
/// under the first valid parent according to the <see cref="_nestingInstructions"/> <see cref="Converted"/> dictionary.
/// </summary>
/// <remarks>
/// A parent is considered valid if
/// 1. Is non null
/// 2. Is in the <see cref="Converted"/> dictionary
/// 3. Has (or can dynamically accept) a <see cref="IList"/> typed property with the propName specified by the <see cref="_nestingInstructions"/> item
/// 4. Said <see cref="IList"/> can accept the <paramref name="current"/> object's type
/// </remarks>
/// <param name="current"></param>
/// <param name="rootCommitObject"></param>
/// <exception cref="InvalidOperationException">Thrown when no valid parent was found for <parameref name="current"/> given <see cref="_nestingInstructions"/></exception>
protected void ApplyRelationship(Base current, Base rootCommitObject)
{
var instructions = _nestingInstructions[current];
foreach (var instruction in instructions)
{
if (instruction.ParentApplicationId is null)
{
continue;
}
Base? parent;
if (instruction.ParentApplicationId == ROOT)
{
parent = rootCommitObject;
}
else
{
Converted.TryGetValue(instruction.ParentApplicationId, out parent);
}
if (parent is null)
{
continue;
}
try
{
instruction.Nest(parent, current);
return;
}
catch (Exception ex) when (!ex.IsFatal())
{
// A parent was found, but it was invalid (Likely because of a type mismatch on a `elements` property)
SpeckleLog.Logger.Warning(ex, "Failed to add object {speckleType} to a converted parent", current.GetType());
}
}
throw new InvalidOperationException(
$"Could not find a valid parent for object of type {current.GetType()}. Checked {instructions.Count} potential parent, and non were converted!"
);
}
protected static void NestUnderElementsProperty(Base parent, Base child)
{
NestUnderProperty(parent, child, ELEMENTS);
}
protected static void NestUnderProperty(Base parent, Base child, string property)
{
if (parent.GetDetachedProp(property) is not IList elements)
{
elements = new List<Base>();
parent.SetDetachedProp(property, elements);
}
elements.Add(child);
}
[Obsolete("Renamed to " + nameof(ROOT))]
[SuppressMessage("Style", "IDE1006:Naming Styles")]
protected const string Root = ROOT;
[Obsolete("Renamed to " + nameof(Converted))]
[SuppressMessage("Style", "IDE1006:Naming Styles")]
protected IDictionary<string, Base> converted => Converted;
}