From c8b9a2d4d5523f295e4ab70ecdd53b136f659492 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 18 Dec 2024 15:48:09 +0000 Subject: [PATCH 1/2] Autocad using new TypedConverter resolution --- .../AutocadRootToSpeckleConverter.cs | 17 +-- .../AutocadPolycurveToHostConverter.cs | 9 +- .../Registration/ConverterManager.cs | 109 +++++++++++++----- .../Registration/IConverterManager.cs | 7 -- .../Registration/ServiceRegistration.cs | 71 +++++------- .../RootToSpeckleConverter.cs | 22 ++-- .../ToHost/ConverterWithFallback.cs | 15 ++- .../ToHost/ConverterWithoutFallback.cs | 15 +-- 8 files changed, 150 insertions(+), 115 deletions(-) diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/AutocadRootToSpeckleConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/AutocadRootToSpeckleConverter.cs index c6cdfe407..f6f9d09df 100644 --- a/Converters/Autocad/Speckle.Converters.AutocadShared/AutocadRootToSpeckleConverter.cs +++ b/Converters/Autocad/Speckle.Converters.AutocadShared/AutocadRootToSpeckleConverter.cs @@ -9,11 +9,11 @@ namespace Speckle.Converters.Autocad; public class AutocadRootToSpeckleConverter : IRootToSpeckleConverter { - private readonly IConverterManager _toSpeckle; + private readonly IConverterManager _toSpeckle; private readonly IConverterSettingsStore _settingsStore; public AutocadRootToSpeckleConverter( - IConverterManager toSpeckle, + IConverterManager toSpeckle, IConverterSettingsStore settingsStore ) { @@ -23,24 +23,25 @@ IConverterSettingsStore settingsStore public Base Convert(object target) { + + Type type = target.GetType(); if (target is not DBObject dbObject) { throw new ValidationException( - $"Conversion of {target.GetType().Name} to Speckle is not supported. Only objects that inherit from DBObject are." + $"Conversion of {type.Name} to Speckle is not supported. Only objects that inherit from DBObject are." ); } - Type type = dbObject.GetType(); - using (var l = _settingsStore.Current.Document.LockDocument()) { using (var tr = _settingsStore.Current.Document.Database.TransactionManager.StartTransaction()) { - var objectConverter = _toSpeckle.ResolveConverter(type); + var objectConverter = _toSpeckle.GetHostConverter(type); + var interfaceType = typeof(ITypedConverter<,>).MakeGenericType(type, typeof(Base)); + var convertedObject = interfaceType.GetMethod("Convert")!.Invoke(objectConverter, new object[] { dbObject })!; - var convertedObject = objectConverter.Convert(dbObject); tr.Commit(); - return convertedObject; + return (Base)convertedObject!; } } } diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Geometry/AutocadPolycurveToHostConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Geometry/AutocadPolycurveToHostConverter.cs index 3f98ed847..07ed6aca2 100644 --- a/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Geometry/AutocadPolycurveToHostConverter.cs +++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Geometry/AutocadPolycurveToHostConverter.cs @@ -1,12 +1,13 @@ using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; +using Speckle.Objects.Geometry.Autocad; using Speckle.Sdk.Common.Exceptions; using Speckle.Sdk.Models; namespace Speckle.Converters.Autocad2023.ToHost.Geometry; [NameAndRankValue(nameof(SOG.Autocad.AutocadPolycurve), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)] -public class AutocadPolycurveToHostConverter : IToHostTopLevelConverter +public class AutocadPolycurveToHostConverter : IToHostTopLevelConverter, ITypedConverter { private readonly ITypedConverter _polylineConverter; private readonly ITypedConverter _polyline2dConverter; @@ -22,10 +23,10 @@ public AutocadPolycurveToHostConverter( _polyline2dConverter = polyline2dConverter; _polyline3dConverter = polyline3dConverter; } + - public object Convert(Base target) + public object Convert(AutocadPolycurve polycurve) { - SOG.Autocad.AutocadPolycurve polycurve = (SOG.Autocad.AutocadPolycurve)target; switch (polycurve.polyType) { @@ -47,4 +48,6 @@ public object Convert(Base target) throw new ValidationException("Unknown poly type for AutocadPolycurve"); } } + + public object Convert(Base target) => Convert( (SOG.Autocad.AutocadPolycurve)target); } diff --git a/Sdk/Speckle.Converters.Common/Registration/ConverterManager.cs b/Sdk/Speckle.Converters.Common/Registration/ConverterManager.cs index 49a1558fd..4a6fa76f5 100644 --- a/Sdk/Speckle.Converters.Common/Registration/ConverterManager.cs +++ b/Sdk/Speckle.Converters.Common/Registration/ConverterManager.cs @@ -1,48 +1,101 @@ -using System.Collections.Concurrent; using Microsoft.Extensions.DependencyInjection; +using Speckle.InterfaceGenerator; +using Speckle.Sdk.Common; using Speckle.Sdk.Common.Exceptions; namespace Speckle.Converters.Common.Registration; -public class ConverterManager(ConcurrentDictionary converterTypes, IServiceProvider serviceProvider) - : IConverterManager +[GenerateAutoInterface] +public class ConverterManager(IReadOnlyDictionary<(Type, Type), Type> toSpeckleConverters, + IReadOnlyDictionary<(Type, Type), Type> toHostConverters, + IServiceProvider serviceProvider) : IConverterManager { - public string Name => typeof(T).Name; + private readonly Dictionary _hostConverterCache = new(); + private readonly Dictionary _speckleConverterCache = new(); + + public (object, Type) GetSpeckleConverter(Type sourceType) + { + Type? destinationType; + Type? converterType; + if (!_speckleConverterCache.TryGetValue(sourceType, out var cached)) + { + (destinationType, converterType) = GetConverter(sourceType, toSpeckleConverters); + _speckleConverterCache.Add(sourceType, (destinationType, converterType)); + } + else + { + (destinationType, converterType) = cached; + } + + if (converterType is null) + { + throw new ConversionNotSupportedException( + $"No conversion found for {sourceType.Name}"); + } + + return (ActivatorUtilities.CreateInstance(serviceProvider, converterType), destinationType.NotNull()); + } - public T ResolveConverter(Type type, bool recursive = true) + public (object, Type) GetHostConverter(Type sourceType) { - var currentType = type; - while (true) + Type? destinationType; + Type? converterType; + if (!_hostConverterCache.TryGetValue(sourceType, out var cached)) { - var typeName = currentType.Name; - var converter = GetConverterByType(typeName); - if (converter is null && recursive) - { - var baseType = currentType.BaseType; - currentType = baseType; + (destinationType, converterType) = GetConverter(sourceType, toHostConverters); + _hostConverterCache.Add(sourceType, (destinationType, converterType)); + } + else + { + (destinationType, converterType) = cached; + } - if (currentType == null) - { - throw new ConversionNotSupportedException($"No conversion found for {type.Name} or any of its base types"); - } - } - else if (converter is null) - { - throw new ConversionNotSupportedException($"No conversion found for {type.Name}"); - } - else + if (converterType is null) + { + throw new ConversionNotSupportedException( + $"No conversion found for {sourceType.Name}"); + } + + return (ActivatorUtilities.CreateInstance(serviceProvider, converterType), destinationType.NotNull()); + } + + private (Type?, Type?) GetConverter(Type sourceType, IReadOnlyDictionary<(Type, Type), Type> converters) + { + Type? mostBasicDestinationType = null; + Type? mostBasicConverterType = null; + foreach (var (d, converterType) in GetConverters(sourceType, converters)) + { + if (mostBasicDestinationType is null || d.IsAssignableFrom(mostBasicDestinationType)) { - return converter; + mostBasicDestinationType = d; + mostBasicConverterType = converterType; } } + + return (mostBasicDestinationType, mostBasicConverterType); } - private T? GetConverterByType(string typeName) + private IEnumerable<(Type, Type)> GetConverters(Type sourceType, IReadOnlyDictionary<(Type, Type), Type> converters) { - if (converterTypes.TryGetValue(typeName, out var converter)) + foreach (var (s, d) in converters.Keys) { - return (T)ActivatorUtilities.CreateInstance(serviceProvider, converter); + Type? currentSourceType = s; + while (true) + { + if (currentSourceType == sourceType) + { + yield return (d, converters[(s, d)]); + } + var baseType = currentSourceType.BaseType; + currentSourceType = baseType; + + if (currentSourceType == null) + { + break; + } + + } } - return default; } } + diff --git a/Sdk/Speckle.Converters.Common/Registration/IConverterManager.cs b/Sdk/Speckle.Converters.Common/Registration/IConverterManager.cs index 6be889288..e69de29bb 100644 --- a/Sdk/Speckle.Converters.Common/Registration/IConverterManager.cs +++ b/Sdk/Speckle.Converters.Common/Registration/IConverterManager.cs @@ -1,7 +0,0 @@ -namespace Speckle.Converters.Common.Registration; - -public interface IConverterManager -{ - public string Name { get; } - public T ResolveConverter(Type type, bool recursive = true); -} diff --git a/Sdk/Speckle.Converters.Common/Registration/ServiceRegistration.cs b/Sdk/Speckle.Converters.Common/Registration/ServiceRegistration.cs index d294b909a..f3be6e861 100644 --- a/Sdk/Speckle.Converters.Common/Registration/ServiceRegistration.cs +++ b/Sdk/Speckle.Converters.Common/Registration/ServiceRegistration.cs @@ -1,14 +1,15 @@ -using System.Collections.Concurrent; -using System.Reflection; +using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Speckle.Converters.Common.Objects; using Speckle.Converters.Common.ToHost; -using Speckle.Sdk.Common; +using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; namespace Speckle.Converters.Common.Registration; public static class ServiceRegistration { + private static readonly Type s_base = typeof(ISpeckleObject); public static void AddRootCommon( this IServiceCollection serviceCollection, Assembly converterAssembly @@ -25,9 +26,7 @@ This will require consolidating across other connectors. serviceCollection.AddScoped(); serviceCollection.AddScoped(); //Register as self, only the `ConverterWithFallback` needs it - - serviceCollection.AddConverters(converterAssembly); - serviceCollection.AddConverters(converterAssembly); + serviceCollection.AddConverters(converterAssembly); } public static IServiceCollection AddApplicationConverters( @@ -41,50 +40,32 @@ Assembly converterAssembly return serviceCollection; } - public static void AddConverters(this IServiceCollection serviceCollection, Assembly converterAssembly) + public static void AddConverters(this IServiceCollection serviceCollection, Assembly converterAssembly) { - ConcurrentDictionary converterTypes = new(); - var types = converterAssembly.ExportedTypes.Where(x => x.GetInterfaces().Contains(typeof(T))); - - // we only care about named types - var byName = types - .Where(x => x.GetCustomAttribute() != null) - .Select(x => - { - var nameAndRank = x.GetCustomAttribute().NotNull(); - - return (name: nameAndRank.Name, rank: nameAndRank.Rank, type: x); - }) - .ToList(); - - // we'll register the types accordingly - var names = byName.Select(x => x.name).Distinct(); - foreach (string name in names) + Dictionary<(Type, Type), Type> toSpeckleConverters = new(); + Dictionary<(Type, Type), Type> toHostConverters = new(); + foreach (var type in converterAssembly.ExportedTypes) { - var namedTypes = byName.Where(x => x.name == name).OrderByDescending(y => y.rank).ToList(); - - // first type found - var first = namedTypes[0]; - - // POC: may need to be instance per lifecycle scope - converterTypes.TryAdd(first.name, first.type); - - // POC: not sure yet if... - // * This should be an array of types - // * Whether the scope should be modified or modifiable - // * Whether this is in the write project... hmmm - // POC: IsAssignableFrom() - var secondaryType = first.type.GetInterface(typeof(ITypedConverter<,>).Name); - // POC: should we explode if no found? - if (secondaryType != null) + foreach (var interfaceType in type.GetInterfaces()) { - converterTypes.TryAdd(first.name, secondaryType); + if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(ITypedConverter<,>)) + { + var genericTypes = interfaceType.GenericTypeArguments; + if (s_base.IsAssignableFrom(genericTypes[0]) ) + { + toHostConverters.Add((genericTypes[0], genericTypes[1]), type); + } else if (s_base.IsAssignableFrom(genericTypes[1])) + { + toSpeckleConverters.Add((genericTypes[0], genericTypes[1]), type); + } + else + { + throw new ConversionException(type.Name + " is not a valid converter."); + } + } } - - // register subsequent types with rank - namedTypes.RemoveAt(0); } - serviceCollection.AddScoped>(sp => new ConverterManager(converterTypes, sp)); + serviceCollection.AddScoped(sp => new ConverterManager(toSpeckleConverters, toHostConverters, sp)); } public static void RegisterRawConversions(this IServiceCollection serviceCollection, Assembly conversionAssembly) diff --git a/Sdk/Speckle.Converters.Common/RootToSpeckleConverter.cs b/Sdk/Speckle.Converters.Common/RootToSpeckleConverter.cs index b087e5f08..ec1a25cf2 100644 --- a/Sdk/Speckle.Converters.Common/RootToSpeckleConverter.cs +++ b/Sdk/Speckle.Converters.Common/RootToSpeckleConverter.cs @@ -1,28 +1,32 @@ + using Speckle.Converters.Common.Objects; using Speckle.Converters.Common.Registration; -using Speckle.InterfaceGenerator; using Speckle.Sdk.Models; namespace Speckle.Converters.Common; -[GenerateAutoInterface] +public interface IRootToSpeckleConverter +{ + Base Convert(object target); +} + public class RootToSpeckleConverter : IRootToSpeckleConverter { - private readonly IConverterManager _toSpeckle; + private readonly IConverterManager _toSpeckle; - public RootToSpeckleConverter(IConverterManager toSpeckle) + public RootToSpeckleConverter(IConverterManager toSpeckle) { _toSpeckle = toSpeckle; } public Base Convert(object target) - { + { Type type = target.GetType(); - var objectConverter = _toSpeckle.ResolveConverter(type); - - var convertedObject = objectConverter.Convert(target); + var objectConverter = _toSpeckle.GetHostConverter(type); + var interfaceType = typeof(ITypedConverter<,>).MakeGenericType(type, typeof(Base)); + var convertedObject = interfaceType.GetMethod("Convert")!.Invoke(objectConverter, new object[] { target })!; - return convertedObject; + return (Base)convertedObject; } } diff --git a/Sdk/Speckle.Converters.Common/ToHost/ConverterWithFallback.cs b/Sdk/Speckle.Converters.Common/ToHost/ConverterWithFallback.cs index f7730f598..1d42e1344 100644 --- a/Sdk/Speckle.Converters.Common/ToHost/ConverterWithFallback.cs +++ b/Sdk/Speckle.Converters.Common/ToHost/ConverterWithFallback.cs @@ -41,15 +41,13 @@ public ConverterWithFallback(ConverterWithoutFallback baseConverter, ILoggerThrown when no conversion is found for . public object Convert(Base target) { - Type type = target.GetType(); - try { return _baseConverter.Convert(target); } catch (ConversionNotSupportedException e) { - _logger.LogInformation(e, "Attempt to find conversion for type {type} failed", type); + _logger.LogInformation(e, "Attempt to find conversion for type {type} failed", target.GetType()); } // Fallback to display value if it exists. @@ -59,17 +57,18 @@ public object Convert(Base target) { // TODO: I'm not sure if this should be a ConversionNotSupported instead, but it kinda mixes support + validation so I went for normal conversion exception throw new ConversionException( - $"No direct conversion found for type {type} and it's fallback display value was null/empty" + $"No direct conversion found for type { target.GetType()} and it's fallback display value was null/empty" ); } - return FallbackToDisplayValue(displayValue); // 1 - many mapping + throw new NotImplementedException(); + // return FallbackToDisplayValue(displayValue); // 1 - many mapping } - private object FallbackToDisplayValue(IReadOnlyList displayValue) + /* private TOut FallbackToDisplayValue(IReadOnlyList displayValue) { var tempDisplayableObject = new DisplayableObject(displayValue); - var conversionResult = _baseConverter.Convert(tempDisplayableObject); + var conversionResult = _baseConverter.Convert(tempDisplayableObject); // if the host app returns a list of objects as the result of the fallback conversion, we zip them together with the original base display value objects that generated them. if (conversionResult is IEnumerable result) @@ -79,5 +78,5 @@ private object FallbackToDisplayValue(IReadOnlyList displayValue) // if not, and the host app "merges" together somehow multiple display values into one entity, we return that. return conversionResult; - } + }*/ } diff --git a/Sdk/Speckle.Converters.Common/ToHost/ConverterWithoutFallback.cs b/Sdk/Speckle.Converters.Common/ToHost/ConverterWithoutFallback.cs index 181eb8306..8fd242316 100644 --- a/Sdk/Speckle.Converters.Common/ToHost/ConverterWithoutFallback.cs +++ b/Sdk/Speckle.Converters.Common/ToHost/ConverterWithoutFallback.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Speckle.Converters.Common.Extensions; using Speckle.Converters.Common.Objects; using Speckle.Converters.Common.Registration; using Speckle.Sdk.Models; @@ -9,16 +8,16 @@ namespace Speckle.Converters.Common.ToHost; // POC: CNX-9394 Find a better home for this outside `DependencyInjection` project /// /// Provides an implementation for -/// that resolves a via the injected +/// that resolves a via the injected /// /// public sealed class ConverterWithoutFallback : IRootToHostConverter { - private readonly IConverterManager _toHost; + private readonly IConverterManager _toHost; private readonly ILogger _logger; public ConverterWithoutFallback( - IConverterManager converterResolver, + IConverterManager converterResolver, ILogger logger ) { @@ -28,8 +27,10 @@ ILogger logger public object Convert(Base target) { - var converter = _toHost.ResolveConverter(target.GetType()); - object result = converter.ConvertAndLog(target, _logger); - return result; + var type = target.GetType(); + var (objectConverter, destinationType) = _toHost.GetHostConverter(type); + var interfaceType = typeof(ITypedConverter<,>).MakeGenericType(type, destinationType); + var convertedObject = interfaceType.GetMethod("Convert")!.Invoke(objectConverter, new object[] { target })!; + return convertedObject; } } From 98d50008e1e6fdccdd6e83b81f16450b5ff7de14 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 18 Dec 2024 16:19:22 +0000 Subject: [PATCH 2/2] Use type converters for speckle and host objects --- .../Receive/AutocadHostObjectBuilder.cs | 4 +- .../Send/AutocadRootObjectBaseBuilder.cs | 4 +- .../Send/AutocadRootObjectBuilder.cs | 2 +- .../AutocadRootToSpeckleConverter.cs | 20 ++--- .../ConverterWithFallback.cs | 58 +++++++++++++ .../HostConverter.cs | 42 ++++++++++ .../IRootElementProvider.cs | 6 -- .../IRootToHostConverter.cs | 8 -- .../Registration/ServiceRegistration.cs | 12 ++- .../RootToSpeckleConverter.cs | 32 -------- .../SpeckleConverter.cs | 12 +++ .../ToHost/ConverterWithFallback.cs | 82 ------------------- .../ToHost/ConverterWithoutFallback.cs | 36 -------- 13 files changed, 129 insertions(+), 189 deletions(-) create mode 100644 Sdk/Speckle.Converters.Common/ConverterWithFallback.cs create mode 100644 Sdk/Speckle.Converters.Common/HostConverter.cs delete mode 100644 Sdk/Speckle.Converters.Common/IRootElementProvider.cs delete mode 100644 Sdk/Speckle.Converters.Common/IRootToHostConverter.cs delete mode 100644 Sdk/Speckle.Converters.Common/RootToSpeckleConverter.cs create mode 100644 Sdk/Speckle.Converters.Common/SpeckleConverter.cs delete mode 100644 Sdk/Speckle.Converters.Common/ToHost/ConverterWithFallback.cs delete mode 100644 Sdk/Speckle.Converters.Common/ToHost/ConverterWithoutFallback.cs diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs index 43e5d3dea..fb1cce9b9 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs @@ -23,7 +23,7 @@ namespace Speckle.Connectors.Autocad.Operations.Receive; public class AutocadHostObjectBuilder : IHostObjectBuilder { private readonly AutocadLayerBaker _layerBaker; - private readonly IRootToHostConverter _converter; + private readonly IHostConverter _converter; private readonly ISyncToThread _syncToThread; private readonly AutocadGroupBaker _groupBaker; private readonly AutocadMaterialBaker _materialBaker; @@ -33,7 +33,7 @@ public class AutocadHostObjectBuilder : IHostObjectBuilder private readonly RootObjectUnpacker _rootObjectUnpacker; public AutocadHostObjectBuilder( - IRootToHostConverter converter, + IHostConverter converter, AutocadLayerBaker layerBaker, AutocadGroupBaker groupBaker, AutocadInstanceBaker instanceBaker, diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObjectBaseBuilder.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObjectBaseBuilder.cs index 9c8c77e15..7d393ea26 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObjectBaseBuilder.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObjectBaseBuilder.cs @@ -18,7 +18,7 @@ namespace Speckle.Connectors.Autocad.Operations.Send; public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder { - private readonly IRootToSpeckleConverter _converter; + private readonly ISpeckleConverter _converter; private readonly string[] _documentPathSeparator = ["\\"]; private readonly ISendConversionCache _sendConversionCache; private readonly AutocadInstanceUnpacker _instanceUnpacker; @@ -29,7 +29,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder _settingsStore; public AutocadRootToSpeckleConverter( IConverterManager toSpeckle, IConverterSettingsStore settingsStore - ) + ) : base(toSpeckle) { - _toSpeckle = toSpeckle; _settingsStore = settingsStore; } - public Base Convert(object target) + public override Base Convert(object target) { - Type type = target.GetType(); - if (target is not DBObject dbObject) + if (target is not DBObject) { + Type type = target.GetType(); throw new ValidationException( $"Conversion of {type.Name} to Speckle is not supported. Only objects that inherit from DBObject are." ); @@ -36,12 +33,9 @@ public Base Convert(object target) { using (var tr = _settingsStore.Current.Document.Database.TransactionManager.StartTransaction()) { - var objectConverter = _toSpeckle.GetHostConverter(type); - var interfaceType = typeof(ITypedConverter<,>).MakeGenericType(type, typeof(Base)); - var convertedObject = interfaceType.GetMethod("Convert")!.Invoke(objectConverter, new object[] { dbObject })!; - + var convertedObject= base.Convert(target); tr.Commit(); - return (Base)convertedObject!; + return convertedObject; } } } diff --git a/Sdk/Speckle.Converters.Common/ConverterWithFallback.cs b/Sdk/Speckle.Converters.Common/ConverterWithFallback.cs new file mode 100644 index 000000000..d6e311374 --- /dev/null +++ b/Sdk/Speckle.Converters.Common/ConverterWithFallback.cs @@ -0,0 +1,58 @@ +using System.Collections; +using Microsoft.Extensions.Logging; +using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Extensions; + +namespace Speckle.Converters.Common; + +public sealed class ConverterWithFallback : IHostConverter +{ + private readonly ILogger _logger; + private readonly HostConverter _baseConverter; + + public ConverterWithFallback(HostConverter baseConverter, ILogger logger) + { + _logger = logger; + _baseConverter = baseConverter; + } + + public object Convert(Base target) + { + try + { + return _baseConverter.Convert(target); + } + catch (ConversionNotSupportedException e) + { + _logger.LogInformation(e, "Attempt to find conversion for type {type} failed", target.GetType()); + } + + // Fallback to display value if it exists. + var displayValue = target.TryGetDisplayValue(); + + if (displayValue == null || (displayValue is IList && !displayValue.Any())) + { + // TODO: I'm not sure if this should be a ConversionNotSupported instead, but it kinda mixes support + validation so I went for normal conversion exception + throw new ConversionException( + $"No direct conversion found for type { target.GetType()} and it's fallback display value was null/empty" + ); + } + + return FallbackToDisplayValue(displayValue); // 1 - many mapping + } + private object FallbackToDisplayValue(IReadOnlyList displayValue) + { + var tempDisplayableObject = new DisplayableObject(displayValue); + var conversionResult = _baseConverter.Convert(tempDisplayableObject); + + // if the host app returns a list of objects as the result of the fallback conversion, we zip them together with the original base display value objects that generated them. + if (conversionResult is IEnumerable result) + { + return result.Zip(displayValue, (a, b) => (a, b)); + } + + // if not, and the host app "merges" together somehow multiple display values into one entity, we return that. + return conversionResult; + } +} diff --git a/Sdk/Speckle.Converters.Common/HostConverter.cs b/Sdk/Speckle.Converters.Common/HostConverter.cs new file mode 100644 index 000000000..4cb352950 --- /dev/null +++ b/Sdk/Speckle.Converters.Common/HostConverter.cs @@ -0,0 +1,42 @@ +using Speckle.Converters.Common.Objects; +using Speckle.Converters.Common.Registration; +using Speckle.InterfaceGenerator; +using Speckle.Sdk.Common; +using Speckle.Sdk.Models; + +namespace Speckle.Converters.Common; + +[GenerateAutoInterface] +public class HostConverter(IConverterManager converterManager) : ConverterBase(converterManager), IHostConverter +{ + + public object Convert(Base target) => Convert(target, (manager, sourceType) => manager.GetHostConverter(sourceType)); +} + +public abstract class ConverterBase(IConverterManager converterManager) +{ + private readonly Dictionary _sourceTypeToInvokerType = new(); + private static readonly object s_emptyObject = new(); + private readonly object[] _invokerArgs = [s_emptyObject]; + + protected object Convert(object target, Func getConverter) + { + var type = target.GetType(); + (object objectConverter, Type destinationType) = getConverter(converterManager, type); + if (!_sourceTypeToInvokerType.TryGetValue(type, out var invokerType)) + { + invokerType = typeof(ITypedConverter<,>).MakeGenericType(type, destinationType); + _sourceTypeToInvokerType.Add(type, invokerType); + } + _invokerArgs[0] = target; + try + { + var convertedObject = invokerType.GetMethod("Convert").NotNull().Invoke(objectConverter, _invokerArgs).NotNull(); + return convertedObject; + } + finally + { + _invokerArgs[0] = s_emptyObject; + } + } +} diff --git a/Sdk/Speckle.Converters.Common/IRootElementProvider.cs b/Sdk/Speckle.Converters.Common/IRootElementProvider.cs deleted file mode 100644 index e183c154a..000000000 --- a/Sdk/Speckle.Converters.Common/IRootElementProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Speckle.Converters.Common; - -public interface IRootElementProvider -{ - Type GetRootType(); -} diff --git a/Sdk/Speckle.Converters.Common/IRootToHostConverter.cs b/Sdk/Speckle.Converters.Common/IRootToHostConverter.cs deleted file mode 100644 index 2d8c52fad..000000000 --- a/Sdk/Speckle.Converters.Common/IRootToHostConverter.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Speckle.Sdk.Models; - -namespace Speckle.Converters.Common; - -public interface IRootToHostConverter -{ - object Convert(Base target); -} diff --git a/Sdk/Speckle.Converters.Common/Registration/ServiceRegistration.cs b/Sdk/Speckle.Converters.Common/Registration/ServiceRegistration.cs index f3be6e861..7be65f160 100644 --- a/Sdk/Speckle.Converters.Common/Registration/ServiceRegistration.cs +++ b/Sdk/Speckle.Converters.Common/Registration/ServiceRegistration.cs @@ -1,7 +1,6 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Speckle.Converters.Common.Objects; -using Speckle.Converters.Common.ToHost; using Speckle.Sdk.Common.Exceptions; using Speckle.Sdk.Models; @@ -14,18 +13,17 @@ public static void AddRootCommon( this IServiceCollection serviceCollection, Assembly converterAssembly ) - where TRootToSpeckleConverter : class, IRootToSpeckleConverter + where TRootToSpeckleConverter : class, ISpeckleConverter { - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); /* POC: CNX-9267 Moved the Injection of converters into the converter module. Not sure if this is 100% right, as this doesn't just register the conversions within this converter, but any conversions found in any Speckle.*.dll file. This will require consolidating across other connectors. */ - - - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); //Register as self, only the `ConverterWithFallback` needs it + + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); //Register as self, only the `ConverterWithFallback` needs it serviceCollection.AddConverters(converterAssembly); } diff --git a/Sdk/Speckle.Converters.Common/RootToSpeckleConverter.cs b/Sdk/Speckle.Converters.Common/RootToSpeckleConverter.cs deleted file mode 100644 index ec1a25cf2..000000000 --- a/Sdk/Speckle.Converters.Common/RootToSpeckleConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ - -using Speckle.Converters.Common.Objects; -using Speckle.Converters.Common.Registration; -using Speckle.Sdk.Models; - -namespace Speckle.Converters.Common; - -public interface IRootToSpeckleConverter -{ - Base Convert(object target); -} - -public class RootToSpeckleConverter : IRootToSpeckleConverter -{ - private readonly IConverterManager _toSpeckle; - - public RootToSpeckleConverter(IConverterManager toSpeckle) - { - _toSpeckle = toSpeckle; - } - - public Base Convert(object target) - { - Type type = target.GetType(); - - var objectConverter = _toSpeckle.GetHostConverter(type); - var interfaceType = typeof(ITypedConverter<,>).MakeGenericType(type, typeof(Base)); - var convertedObject = interfaceType.GetMethod("Convert")!.Invoke(objectConverter, new object[] { target })!; - - return (Base)convertedObject; - } -} diff --git a/Sdk/Speckle.Converters.Common/SpeckleConverter.cs b/Sdk/Speckle.Converters.Common/SpeckleConverter.cs new file mode 100644 index 000000000..015f4e57d --- /dev/null +++ b/Sdk/Speckle.Converters.Common/SpeckleConverter.cs @@ -0,0 +1,12 @@ + +using Speckle.Converters.Common.Registration; +using Speckle.InterfaceGenerator; +using Speckle.Sdk.Models; + +namespace Speckle.Converters.Common; + +[GenerateAutoInterface] +public class SpeckleConverter(IConverterManager converterManager) : ConverterBase(converterManager),ISpeckleConverter +{ + public virtual Base Convert(object target) => (Base)Convert(target, (manager, sourceType) => manager.GetSpeckleConverter(sourceType)); +} diff --git a/Sdk/Speckle.Converters.Common/ToHost/ConverterWithFallback.cs b/Sdk/Speckle.Converters.Common/ToHost/ConverterWithFallback.cs deleted file mode 100644 index 1d42e1344..000000000 --- a/Sdk/Speckle.Converters.Common/ToHost/ConverterWithFallback.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Collections; -using Microsoft.Extensions.Logging; -using Speckle.Sdk.Common.Exceptions; -using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Extensions; - -namespace Speckle.Converters.Common.ToHost; - -// POC: CNX-9394 Find a better home for this outside `DependencyInjection` project -/// -/// -///
-/// If no suitable converter conversion is found, and the target object has a displayValue property -/// a converter with strong name of is resolved for. -///
-/// -public sealed class ConverterWithFallback : IRootToHostConverter -{ - private readonly ILogger _logger; - private readonly ConverterWithoutFallback _baseConverter; - - public ConverterWithFallback(ConverterWithoutFallback baseConverter, ILogger logger) - { - _logger = logger; - _baseConverter = baseConverter; - } - - /// - /// Converts a instance to a host object. - /// - /// The instance to convert. - /// The converted host object. - /// Fallbacks to display value if a direct conversion is not possible. - /// - /// The conversion is done in the following order of preference: - /// 1. Direct conversion using the . - /// 2. Fallback to display value using the method, if a direct conversion is not possible. - /// - /// If the direct conversion is not available and there is no displayValue, a is thrown. - /// - /// Thrown when no conversion is found for . - public object Convert(Base target) - { - try - { - return _baseConverter.Convert(target); - } - catch (ConversionNotSupportedException e) - { - _logger.LogInformation(e, "Attempt to find conversion for type {type} failed", target.GetType()); - } - - // Fallback to display value if it exists. - var displayValue = target.TryGetDisplayValue(); - - if (displayValue == null || (displayValue is IList && !displayValue.Any())) - { - // TODO: I'm not sure if this should be a ConversionNotSupported instead, but it kinda mixes support + validation so I went for normal conversion exception - throw new ConversionException( - $"No direct conversion found for type { target.GetType()} and it's fallback display value was null/empty" - ); - } - - throw new NotImplementedException(); - // return FallbackToDisplayValue(displayValue); // 1 - many mapping - } - - /* private TOut FallbackToDisplayValue(IReadOnlyList displayValue) - { - var tempDisplayableObject = new DisplayableObject(displayValue); - var conversionResult = _baseConverter.Convert(tempDisplayableObject); - - // if the host app returns a list of objects as the result of the fallback conversion, we zip them together with the original base display value objects that generated them. - if (conversionResult is IEnumerable result) - { - return result.Zip(displayValue, (a, b) => (a, b)); - } - - // if not, and the host app "merges" together somehow multiple display values into one entity, we return that. - return conversionResult; - }*/ -} diff --git a/Sdk/Speckle.Converters.Common/ToHost/ConverterWithoutFallback.cs b/Sdk/Speckle.Converters.Common/ToHost/ConverterWithoutFallback.cs deleted file mode 100644 index 8fd242316..000000000 --- a/Sdk/Speckle.Converters.Common/ToHost/ConverterWithoutFallback.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Extensions.Logging; -using Speckle.Converters.Common.Objects; -using Speckle.Converters.Common.Registration; -using Speckle.Sdk.Models; - -namespace Speckle.Converters.Common.ToHost; - -// POC: CNX-9394 Find a better home for this outside `DependencyInjection` project -/// -/// Provides an implementation for -/// that resolves a via the injected -/// -/// -public sealed class ConverterWithoutFallback : IRootToHostConverter -{ - private readonly IConverterManager _toHost; - private readonly ILogger _logger; - - public ConverterWithoutFallback( - IConverterManager converterResolver, - ILogger logger - ) - { - _toHost = converterResolver; - _logger = logger; - } - - public object Convert(Base target) - { - var type = target.GetType(); - var (objectConverter, destinationType) = _toHost.GetHostConverter(type); - var interfaceType = typeof(ITypedConverter<,>).MakeGenericType(type, destinationType); - var convertedObject = interfaceType.GetMethod("Convert")!.Invoke(objectConverter, new object[] { target })!; - return convertedObject; - } -}