Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy important changes to core from DUI3/alpha to main #3478

Merged
merged 1 commit into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Core/Core/Credentials/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
using System.Threading.Tasks;
using Speckle.Core.Api;
using Speckle.Core.Helpers;
using Speckle.Core.Logging;

namespace Speckle.Core.Credentials;

public class Account : IEquatable<Account>
{
private string _id { get; set; }
private string _id;

/// <remarks>
/// The account id is unique to user and server url.
/// </remarks>
/// <exception cref="InvalidOperationException">Account object invalid: missing required info</exception>
public string id
{
get
Expand All @@ -19,7 +22,7 @@ public string id
{
if (serverInfo == null || userInfo == null)
{
throw new SpeckleException("Incomplete account info: cannot generate id.");
throw new InvalidOperationException("Incomplete account info: cannot generate id.");
}

_id = Crypt.Md5(userInfo.email + serverInfo.url, "X2");
Expand Down
13 changes: 10 additions & 3 deletions Core/Core/Credentials/AccountManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,22 @@ public static string GetDefaultServerUrl()
return serverUrl;
}

/// <param name="id">The Id of the account to fetch</param>
/// <returns></returns>
/// <exception cref="SpeckleAccountManagerException">Account with <paramref name="id"/> was not found</exception>
public static Account GetAccount(string id)
{
return GetAccounts().FirstOrDefault(acc => acc.id == id)
?? throw new SpeckleAccountManagerException($"Account {id} not found");
}

/// <summary>
/// Upgrades an account from the account.serverInfo.movedFrom account to the account.serverInfo.movedTo account
/// </summary>
/// <param name="id">Id of the account to upgrade</param>
public static async Task UpgradeAccount(string id)
{
var account =
GetAccounts().FirstOrDefault(acc => acc.id == id)
?? throw new SpeckleAccountManagerException($"Account {id} not found");
Account account = GetAccount(id);

if (account.serverInfo.migration.movedTo is not Uri upgradeUri)
{
Expand Down
69 changes: 66 additions & 3 deletions Core/Core/Models/Extensions/BaseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,19 +193,19 @@ public static bool IsDisplayableObject(this Base speckleObject)
return speckleObject.TryGetDisplayValue() != null;
}

public static IEnumerable<T>? TryGetDisplayValue<T>(this Base obj)
public static IReadOnlyList<T>? TryGetDisplayValue<T>(this Base obj)
where T : Base
{
var rawDisplayValue = obj["displayValue"] ?? obj["@displayValue"];
return rawDisplayValue switch
{
T b => new List<T> { b },
IEnumerable enumerable => enumerable.OfType<T>(),
IReadOnlyList<T> list => list,
_ => null
};
}

public static IEnumerable<Base>? TryGetDisplayValue(this Base obj)
public static IReadOnlyList<Base>? TryGetDisplayValue(this Base obj)
{
return TryGetDisplayValue<Base>(obj);
}
Expand All @@ -226,4 +226,67 @@ public static bool IsDisplayableObject(this Base speckleObject)
{
return TryGetParameters<Base>(obj);
}

/// <summary>
/// A variation of the OG Traversal extension from Alan, but with tracking the object path as well.
/// </summary>
/// <param name="recursionBreaker"> Delegate condition to stop traverse.</param>
/// <returns>List of base objects with their collection path.</returns>
public static IEnumerable<(string[], Base)> TraverseWithPath(this Base root, BaseRecursionBreaker recursionBreaker)
{
var stack = new Stack<(List<string>, Base)>();
stack.Push((new List<string>(), root));

while (stack.Count > 0)
{
(List<string> path, Base current) = stack.Pop();
yield return (path.ToArray(), current);

if (recursionBreaker(current))
{
continue;
}

foreach (string child in current.GetDynamicMemberNames())
{
// NOTE: we can store collections rather than just path names. Where we have an actual collection, use that, where not, create a mock one based on the prop name
var localPathFragment = child;
if (current is Collection { name: { } } c)
{
localPathFragment = c.name;
}

var newPath = new List<string>(path) { localPathFragment };
switch (current[child])
{
case Base o:
stack.Push((newPath, o));
break;
case IDictionary dictionary:
{
foreach (object obj in dictionary.Keys)
{
if (obj is Base b)
{
stack.Push((newPath, b));
}
}

break;
}
case IList collection:
{
foreach (object obj in collection)
{
if (obj is Base b)
{
stack.Push((newPath, b));
}
}
break;
}
}
}
}
}
}
133 changes: 82 additions & 51 deletions Core/Core/Models/GraphTraversal/DefaultTraversal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,59 +14,15 @@ namespace Speckle.Core.Models.GraphTraversal;
)]
public static class DefaultTraversal
{
/// <summary>
/// Default traversal rule that ideally should be used by all connectors
/// </summary>
/// <remarks>
/// Treats convertable objects <see cref="ISpeckleConverter.CanConvertToNative"/> and objects with displayValues as "convertable" such that only elements and dynamic props will be traversed
/// </remarks>
/// <param name="converter"></param>
/// <returns></returns>
public static GraphTraversal CreateTraverseFunc(ISpeckleConverter converter)
public static GraphTraversal CreateTraversalFunc()
{
var convertableRule = TraversalRule
.NewTraversalRule()
.When(converter.CanConvertToNative)
.When(b => b.GetType() != typeof(Base))
.When(HasDisplayValue)
.ContinueTraversing(_ => ElementsPropAliases);

return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule);
}

/// <summary>
/// Traverses until finds a convertable object then HALTS deeper traversal
/// </summary>
/// <remarks>
/// Current Revit connector does traversal,
/// so this traversal is a shallow traversal for directly convertable objects,
/// and a deep traversal for all other types
/// </remarks>
/// <param name="converter"></param>
/// <returns></returns>
public static GraphTraversal CreateRevitTraversalFunc(ISpeckleConverter converter)
{
var convertableRule = TraversalRule
.NewTraversalRule()
.When(converter.CanConvertToNative)
.When(HasDisplayValue)
.ContinueTraversing(None);

return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule);
}

/// <summary>
/// Traverses until finds a convertable object (or fallback) then traverses members
/// </summary>
/// <param name="converter"></param>
/// <returns></returns>
public static GraphTraversal CreateBIMTraverseFunc(ISpeckleConverter converter)
{
var bimElementRule = TraversalRule
.NewTraversalRule()
.When(converter.CanConvertToNative)
.ContinueTraversing(ElementsAliases);

return new GraphTraversal(bimElementRule, s_ignoreResultsRule, DefaultRule);
return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule.ShouldReturnToOutput(false));
}

//These functions are just meant to make the syntax of defining rules less verbose, they are likely to change frequently/be restructured
Expand All @@ -78,10 +34,8 @@ public static GraphTraversal CreateBIMTraverseFunc(ISpeckleConverter converter)
.When(o => o.speckle_type.Contains("Objects.Structural.Results"))
.ContinueTraversing(None);

public static readonly ITraversalRule DefaultRule = TraversalRule
.NewTraversalRule()
.When(_ => true)
.ContinueTraversing(Members());
public static ITraversalBuilderReturn DefaultRule =>
TraversalRule.NewTraversalRule().When(_ => true).ContinueTraversing(Members());

public static readonly IReadOnlyList<string> ElementsPropAliases = new[] { "elements", "@elements" };

Expand Down Expand Up @@ -158,6 +112,8 @@ public static IEnumerable<string> DisplayValueAndElementsAliases(Base _)

#endregion

#region Legacy function varients

[Obsolete("Renamed to " + nameof(ElementsPropAliases))]
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Obsolete")]
public static IReadOnlyList<string> elementsPropAliases => ElementsPropAliases;
Expand All @@ -178,4 +134,79 @@ public static IEnumerable<string> DisplayValueAndElementsAliases(Base _)
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Obsolete")]
[SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Obsolete")]
public static string[] displayValueAndElementsPropAliases => DisplayValueAndElementsPropAliases;

/// <summary><inheritdoc cref="CreateLegacyTraverseFunc"/></summary>
/// <remarks><inheritdoc cref="CreateLegacyTraverseFunc"/></remarks>
/// <param name="converter"></param>
/// <returns></returns>
[Obsolete($"Consider using {nameof(CreateTraversalFunc)}")]
public static GraphTraversal CreateTraverseFunc(ISpeckleConverter converter)
{
return CreateLegacyTraverseFunc(converter.CanConvertToNative);
}

/// <summary>
/// Legacy traversal rule that was dependent on the converter
/// </summary>
/// <remarks>
/// Treats convertable objects <see cref="ISpeckleConverter.CanConvertToNative"/> and objects with displayValues as "convertable" such that only elements and dynamic props will be traversed
/// New code should use <see cref="CreateTraversalFunc"/> instead.
/// </remarks>
/// <param name="canConvertToNative"></param>
/// <returns></returns>
[Obsolete($"Consider using {nameof(CreateTraversalFunc)}")]
public static GraphTraversal CreateLegacyTraverseFunc(Func<Base, bool> canConvertToNative)
{
var convertableRule = TraversalRule
.NewTraversalRule()
.When(b => canConvertToNative(b))
.When(HasDisplayValue)
.ContinueTraversing(_ => ElementsPropAliases);

return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule);
}

/// <summary>
/// Traverses until finds a convertable object then HALTS deeper traversal
/// </summary>
/// <remarks>
/// The DUI2 Revit connector does traversal,
/// so this traversal is a shallow traversal for directly convertable objects,
/// and a deep traversal for all other types
/// New code should use <see cref="CreateTraversalFunc"/> instead.
/// </remarks>
/// <param name="converter"></param>
/// <returns></returns>
[Obsolete($"Consider using {nameof(CreateTraversalFunc)}")]
public static GraphTraversal CreateRevitTraversalFunc(ISpeckleConverter converter)
{
var convertableRule = TraversalRule
.NewTraversalRule()
.When(converter.CanConvertToNative)
.When(HasDisplayValue)
.ContinueTraversing(None);

return new GraphTraversal(convertableRule, s_ignoreResultsRule, DefaultRule);
}

/// <summary>
/// Traverses until finds a convertable object (or fallback) then traverses members
/// </summary>
/// <remarks>
/// New code should use <see cref="CreateTraversalFunc"/> instead.
/// </remarks>
/// <param name="converter"></param>
/// <returns></returns>
[Obsolete($"Consider using {nameof(CreateTraversalFunc)}")]
public static GraphTraversal CreateBIMTraverseFunc(ISpeckleConverter converter)
{
var bimElementRule = TraversalRule
.NewTraversalRule()
.When(converter.CanConvertToNative)
.ContinueTraversing(ElementsAliases);

return new GraphTraversal(bimElementRule, s_ignoreResultsRule, DefaultRule);
}

#endregion
}
7 changes: 5 additions & 2 deletions Core/Core/Models/GraphTraversal/GraphTraversal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ public IEnumerable<T> Traverse(Base root)
T head = stack[headIndex];
stack.RemoveAt(headIndex);

yield return head;

Base current = head.Current;
var activeRule = GetActiveRuleOrDefault(current);

if (activeRule.ShouldReturn)
{
yield return head;
}

foreach (string childProp in activeRule.MembersToTraverse(current))
{
TraverseMemberToStack(stack, current[childProp], childProp, head);
Expand Down
26 changes: 17 additions & 9 deletions Core/Core/Models/GraphTraversal/ITraversalRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,34 @@ public interface ITraversalRule
/// <param name="o"></param>
/// <returns></returns>
public bool DoesRuleHold(Base o);

/// <summary>
/// When <see langword="false"/>,
/// <see cref="Base"/> objects for which this rule applies,
/// will be filtered out from the traversal output
/// (but still traversed normally, as per the <see cref="MembersToTraverse"/>)
/// </summary>
/// <remarks>
/// This property was added to allow for easier filtering of the return of <see cref="GraphTraversal{T}.Traverse(Base)"/>.
/// Without the option to set some rules as false, it was necessary to duplicate part of the rules in a <see cref="System.Linq.Enumerable.Where{T}(IEnumerable{T},System.Func{T,bool})"/>
/// </remarks>
public bool ShouldReturn { get; }
}

/// <summary>
/// The "traverse none" rule that always holds true
/// </summary>
public sealed class DefaultRule : ITraversalRule
internal sealed class DefaultRule : ITraversalRule
{
private static DefaultRule? s_instance;

private DefaultRule() { }

public static DefaultRule Instance => s_instance ??= new DefaultRule();

public IEnumerable<string> MembersToTraverse(Base b)
{
return Enumerable.Empty<string>();
}
public IEnumerable<string> MembersToTraverse(Base b) => Enumerable.Empty<string>();

public bool DoesRuleHold(Base o) => true;

public bool DoesRuleHold(Base o)
{
return true;
}
public bool ShouldReturn => true;
}
Loading