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

Redo TypedConverters to have one converstion interface #454

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,7 +33,7 @@ public class AutocadHostObjectBuilder : IHostObjectBuilder
private readonly RootObjectUnpacker _rootObjectUnpacker;

public AutocadHostObjectBuilder(
IRootToHostConverter converter,
IHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Speckle.Connectors.Autocad.Operations.Send;

public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadRootObject>
{
private readonly IRootToSpeckleConverter _converter;
private readonly ISpeckleConverter _converter;
private readonly string[] _documentPathSeparator = ["\\"];
private readonly ISendConversionCache _sendConversionCache;
private readonly AutocadInstanceUnpacker _instanceUnpacker;
Expand All @@ -29,7 +29,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
private readonly ISdkActivityFactory _activityFactory;

protected AutocadRootObjectBaseBuilder(
IRootToSpeckleConverter converter,
ISpeckleConverter converter,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class AutocadRootObjectBuilder : AutocadRootObjectBaseBuilder

public AutocadRootObjectBuilder(
AutocadLayerUnpacker layerUnpacker,
IRootToSpeckleConverter converter,
ISpeckleConverter converter,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,39 @@
using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.Common.Registration;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;

namespace Speckle.Converters.Autocad;

public class AutocadRootToSpeckleConverter : IRootToSpeckleConverter
public class AutocadRootToSpeckleConverter : SpeckleConverter
{
private readonly IConverterManager<IToSpeckleTopLevelConverter> _toSpeckle;
private readonly IConverterSettingsStore<AutocadConversionSettings> _settingsStore;

public AutocadRootToSpeckleConverter(
IConverterManager<IToSpeckleTopLevelConverter> toSpeckle,
IConverterManager toSpeckle,
IConverterSettingsStore<AutocadConversionSettings> settingsStore
)
) : base(toSpeckle)
{
_toSpeckle = toSpeckle;
_settingsStore = settingsStore;
}

public Base Convert(object target)
public override Base Convert(object target)
{
if (target is not DBObject dbObject)

if (target is not DBObject)
{
Type type = target.GetType();
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 convertedObject = objectConverter.Convert(dbObject);
var convertedObject= base.Convert(target);
tr.Commit();
return convertedObject;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SOG.Autocad.AutocadPolycurve, object>
{
private readonly ITypedConverter<SOG.Autocad.AutocadPolycurve, ADB.Polyline> _polylineConverter;
private readonly ITypedConverter<SOG.Autocad.AutocadPolycurve, ADB.Polyline2d> _polyline2dConverter;
Expand All @@ -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)
{
Expand All @@ -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);
}
58 changes: 58 additions & 0 deletions Sdk/Speckle.Converters.Common/ConverterWithFallback.cs
Original file line number Diff line number Diff line change
@@ -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<ConverterWithFallback> _logger;
private readonly HostConverter _baseConverter;

public ConverterWithFallback(HostConverter baseConverter, ILogger<ConverterWithFallback> 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<Base>();

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<Base> 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<object> 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;
}
}
42 changes: 42 additions & 0 deletions Sdk/Speckle.Converters.Common/HostConverter.cs
Original file line number Diff line number Diff line change
@@ -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<Type, Type> _sourceTypeToInvokerType = new();
private static readonly object s_emptyObject = new();
private readonly object[] _invokerArgs = [s_emptyObject];

protected object Convert(object target, Func<IConverterManager, Type, (object, Type)> 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;
}
}
}
6 changes: 0 additions & 6 deletions Sdk/Speckle.Converters.Common/IRootElementProvider.cs

This file was deleted.

8 changes: 0 additions & 8 deletions Sdk/Speckle.Converters.Common/IRootToHostConverter.cs

This file was deleted.

109 changes: 81 additions & 28 deletions Sdk/Speckle.Converters.Common/Registration/ConverterManager.cs
Original file line number Diff line number Diff line change
@@ -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<T>(ConcurrentDictionary<string, Type> converterTypes, IServiceProvider serviceProvider)
: IConverterManager<T>
[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<Type, (Type?, Type?)> _hostConverterCache = new();
private readonly Dictionary<Type, (Type?, Type?)> _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;
}
}

Original file line number Diff line number Diff line change
@@ -1,7 +0,0 @@
namespace Speckle.Converters.Common.Registration;

public interface IConverterManager<T>
{
public string Name { get; }
public T ResolveConverter(Type type, bool recursive = true);
}
Loading
Loading