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; }