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;
///
/// Abstract Builder class for a root commit object.
///
/// The native object data type needed as input for building
///
/// 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
///
[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
{
/// Special appId symbol for the root object
protected const string ROOT = "__Root";
private const string ELEMENTS = nameof(Collection.elements);
/// app id -> base
protected IDictionary Converted { get; }
/// Base -> NestingInstructions ordered by priority
private readonly Dictionary> _nestingInstructions = new();
protected CommitObjectBuilder()
{
Converted = new Dictionary();
}
///
/// Given the parameters, builds connector specific
/// to be applied when is called.
///
///
///
public abstract void IncludeObject(Base conversionResult, TNativeObjectData nativeElement);
///
/// Iterates through the converted objects applying
///
///
/// Can be overriden to adjust exactly which objects get automatically applied,
/// or to inject additional items into the dict that should not be automatically applied.
///
///
public virtual void BuildCommitObject(Base rootCommitObject)
{
ApplyRelationships(Converted.Values, rootCommitObject);
}
protected void SetRelationship(Base conversionResult, NestingInstructions nestingInstructions)
{
SetRelationship(conversionResult, new List { nestingInstructions });
}
///
/// Sets information on how a given object should be nested in the commit tree.
/// encodes the order in which we should try and nest the given
/// when is called
///
/// The object to be nested
/// Information about how the object ideally should be nested, in order of priority
protected void SetRelationship(Base conversionResult, IList nestingInstructionsList)
{
string? appId = conversionResult.applicationId;
if (appId != null)
{
Converted[appId] = conversionResult;
}
_nestingInstructions[conversionResult] = nestingInstructionsList;
}
///
/// For each object in
///
///
///
///
protected void ApplyRelationships(IEnumerable 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());
}
}
}
///
/// Will attempt to find and nest the object
/// under the first valid parent according to the dictionary.
///
///
/// A parent is considered valid if
/// 1. Is non null
/// 2. Is in the dictionary
/// 3. Has (or can dynamically accept) a typed property with the propName specified by the item
/// 4. Said can accept the object's type
///
///
///
/// Thrown when no valid parent was found for given
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();
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 converted => Converted;
}