NodePipeline/NodePipeline.Engine.CodeGeneration/NodeFactoryGenerator.cs
2026-01-02 20:55:25 +03:00

836 lines
40 KiB
C#

using System.Linq;
using System.Text;
using NodePipeline.Abstractions.Interfaces.Nodes;
using NodePipeline.Engine.CodeGeneration.Abstractions;
using NodePipeline.Engine.CodeGeneration.Abstractions.Models;
namespace NodePipeline.Engine.CodeGeneration;
internal static class NodeFactoryGenerator
{
public static string Generate(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine("using System.Globalization;");
sb.AppendLine("using NodePipeline.Abstractions;");
sb.AppendLine("using NodePipeline.Abstractions.Exceptions.NodeFactory;");
sb.AppendLine("using NodePipeline.Abstractions.Interfaces;");
sb.AppendLine("using NodePipeline.Abstractions.Interfaces.Nodes;");
sb.AppendLine("using NodePipeline.Configuration.Abstractions.Models.Execute;");
sb.AppendLine("using NodePipeline.Engine.Abstractions;");
sb.AppendLine("using NodePipeline.Engine.Abstractions.Validation;");
sb.AppendLine("using NodePipeline.Engine.NodeFields;");
sb.AppendLine("using NodePipeline.Engine.Utils;");
sb.AppendLine();
sb.AppendLine("// ReSharper disable CheckNamespace");
sb.AppendLine("// ReSharper disable ConvertTypeCheckPatternToNullCheck");
sb.AppendLine("// ReSharper disable HeuristicUnreachableCode");
sb.AppendLine("// ReSharper disable RedundantCast");
sb.AppendLine("// ReSharper disable RedundantNameQualifier");
sb.AppendLine("// ReSharper disable ReturnTypeCanBeNotNullable\n");
sb.AppendLine("// ReSharper disable UnusedMember.Local");
sb.AppendLine("// ReSharper disable UnusedParameter.Local");
sb.AppendLine();
sb.AppendLine("#nullable enable");
sb.AppendLine("#pragma warning disable CS0162 // Unreachable code detected");
sb.AppendLine();
sb.AppendLine("namespace NodePipeline.Engine.Generated;");
sb.AppendLine();
sb.AppendLine("public sealed class GeneratedNodeFactory : INodeFactory");
sb.AppendLine("{");
sb.AppendLine(" public Dictionary<string, Func<INode>> NodeFactories { get; set; } = new();");
sb.AppendLine(
" public IPipelineLocalizationProvider PipelineLocalizationProvider { get; set; } = new PipelineLocalizationProvider();");
sb.AppendLine();
sb.AppendLine(BuildCreateNodeMethod(model));
sb.AppendLine(BuildInitializeNodeFieldsMethod(model));
sb.AppendLine(BuildInitializeNodeFieldsSpecificMethods(model));
sb.AppendLine(BuildGetParameterCodesMethod(model));
sb.AppendLine(BuildParameterCodesForMethods(model));
sb.AppendLine(BuildReadParameterValueMethod(model));
sb.AppendLine(BuildReadParameterValueForMethods(model));
sb.AppendLine(BuildGetNodePortsMethod(model));
sb.AppendLine(BuildGetNodePortsSpecificMethods(model));
sb.AppendLine(BuildGetNodeOutputsMethod(model));
sb.AppendLine(BuildGetNodeOutputsSpecificMethods(model));
sb.AppendLine(BuildConnectPortsMethod(model));
sb.AppendLine(BuildConnectPortsSpecificMethods(model));
sb.AppendLine(BuildConnectPortsStructGenericMethod());
sb.AppendLine(BuildConnectPortsClassGenericMethod());
sb.AppendLine(BuildParseParameterMethod(model));
sb.AppendLine(BuildGetParameterDefaultValueMethod(model));
sb.AppendLine(BuildGetParameterDefaultValueSpecificMethods(model));
sb.AppendLine(BuildSetNodeParametersValuesMethod(model));
sb.AppendLine(BuildSetNodeParametersValuesSpecificMethods(model));
sb.AppendLine(BuildGetNodeTypeMethod(model));
sb.AppendLine(
" private record PortConnectionInfo(bool Connected, object? OutputFieldObj, string FromPortName, string FromNodeType, string FromNodeId);");
sb.AppendLine("}");
sb.AppendLine("#pragma warning restore CS0162 // Unreachable code detected");
sb.AppendLine("#nullable restore");
return sb.ToString();
}
private static string BuildCreateNodeMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
//TODO: add method description
sb.AppendLine(" public INode CreateNode(string pipelineId, NodeConfig config)");
sb.AppendLine(" {");
sb.AppendLine(" INode? node;");
sb.AppendLine(" if (NodeFactories.TryGetValue(config.Type, out var f))");
sb.AppendLine(" {");
sb.AppendLine(" node = f();");
sb.AppendLine(" InitializeNodeFields(pipelineId, node, config.Id);");
sb.AppendLine(" return node;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" switch (config.Type)");
sb.AppendLine(" {");
foreach (var n in model.Nodes)
{
sb.AppendLine($" {NodeGeneratorHelper.GetNodeNameCaseString(n)}");
if (n.HasParameterlessConstructor)
{
sb.AppendLine($" node = new {n.TypeNameFull}();");
sb.AppendLine(" break;");
}
else
{
sb.AppendLine(
" throw new NodeCanNotBeInitializedWithoutFactoryException(pipelineId, config.Type, config.Id);");
}
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeNotFoundException(pipelineId, config.Type, config.Id);");
sb.AppendLine(" }");
sb.AppendLine(" InitializeNodeFields(pipelineId, node, config.Id);");
sb.AppendLine(
" return node ?? throw new NodeWasNotInitializedException(pipelineId, config.Type, config.Id);");
sb.AppendLine(" }");
return sb.ToString();
}
private static string GetSpecificInitializeNodeFieldsMethodName(NodeDescriptor node)
{
return $"Initialize__{node.TypeNameShortSanitized}__NodeFields";
}
private static string BuildInitializeNodeFieldsMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(" private void InitializeNodeFields(string pipelineId, INode node, string nodeId)");
sb.AppendLine(" {");
sb.AppendLine(" switch (node)");
sb.AppendLine(" {");
var i = 1;
foreach (var n in model.Nodes)
{
sb.AppendLine($" case {n.TypeNameFull} t{i}:");
sb.AppendLine($" {GetSpecificInitializeNodeFieldsMethodName(n)}(t{i}, nodeId);");
sb.AppendLine(" break;");
i++;
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
sb.AppendLine(" }");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BuildInitializeNodeFieldsSpecificMethods(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
foreach (var n in model.Nodes)
{
sb.AppendLine(
$" private static void {GetSpecificInitializeNodeFieldsMethodName(n)}({n.TypeNameFull} node, string nodeId)");
sb.AppendLine(" {");
foreach (var f in n.Fields)
{
var dir = f.Direction switch
{
FieldDirection.Input => "FieldDirection.Input",
FieldDirection.Output => "FieldDirection.Output",
_ => "FieldDirection.Parameter"
};
var nodeFieldType = string.Concat(f.ValueType, f.IsNullableValueType ? "?" : string.Empty);
sb.AppendLine(
$" node.{f.PropertyName} = new NodePipeline.Abstractions.Models.NodeField<{nodeFieldType}>()");
sb.AppendLine(" {");
sb.AppendLine($" Name = \"{f.FieldName}\",");
sb.AppendLine(
$" Code = NodeFieldCodeGenerator.GenerateFieldCode(nodeId, \"{f.FieldName}\", {dir}),");
sb.AppendLine($" Direction = {dir},");
sb.AppendLine(" // Description intentionally omitted for runtime");
sb.AppendLine(" };");
}
sb.AppendLine(" }");
sb.AppendLine();
}
return sb.ToString();
}
private static string GetSpecificParameterCodesMethodName(NodeDescriptor node)
{
return $"GetParameterCodesFor__{node.TypeNameShortSanitized}__Node";
}
private static string BuildGetParameterCodesMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(" public IEnumerable<string> GetParameterCodes(string nodeType)");
sb.AppendLine(" {");
sb.AppendLine(" switch (nodeType)");
sb.AppendLine(" {");
foreach (var n in model.Nodes)
{
sb.AppendLine($" {NodeGeneratorHelper.GetNodeNameCaseString(n)}");
sb.AppendLine($" return {GetSpecificParameterCodesMethodName(n)}();");
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeTypeNotFoundException(nodeType);");
sb.AppendLine(" }");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BuildParameterCodesForMethods(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
foreach (var n in model.Nodes)
{
sb.AppendLine($" private IEnumerable<string> {GetSpecificParameterCodesMethodName(n)}()");
sb.AppendLine(" {");
var nodeParameters = n.Fields
.Where(f => f.Direction == FieldDirection.Parameter)
.ToList();
sb.Append(" return");
if (nodeParameters.Count == 0)
{
sb.AppendLine(" [];");
}
else
{
sb.AppendLine(" [");
foreach (var f in n.Fields.Where(f => f.Direction == FieldDirection.Parameter))
sb.AppendLine($" \"{f.FieldName}\",");
sb.AppendLine(" ];");
}
sb.AppendLine(" }");
sb.AppendLine();
}
return sb.ToString();
}
private static string GetSpecificReadParameterValueMethodName(NodeDescriptor node)
{
return $"ReadParameterValueFrom__{node.TypeNameShortSanitized}__Node";
}
private static string BuildReadParameterValueMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(
" public object? ReadParameterValue(string pipelineId, string nodeId, string nodeType, string parameterName, string? valueString)");
sb.AppendLine(" {");
sb.AppendLine(" switch (nodeType)");
sb.AppendLine(" {");
foreach (var n in model.Nodes)
{
sb.AppendLine($" {NodeGeneratorHelper.GetNodeNameCaseString(n)}");
sb.AppendLine(
$" return {GetSpecificReadParameterValueMethodName(n)}(pipelineId, nodeType, nodeId, parameterName, valueString);");
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeTypeNotFoundException(nodeType);");
sb.AppendLine(" }");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BuildReadParameterValueForMethods(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
foreach (var n in model.Nodes)
{
sb.AppendLine(
$" private static object? {GetSpecificReadParameterValueMethodName(n)}(string pipelineId, string nodeType, string nodeId, string parameterName, string? valueString)");
sb.AppendLine(" {");
sb.AppendLine(" switch (parameterName)");
sb.AppendLine(" {");
foreach (var p in n.Fields.Where(f => f.Direction == FieldDirection.Parameter))
{
sb.AppendLine($" case \"{p.FieldName}\":");
sb.AppendLine($" return ParseParameter<{p.ValueType}>(valueString);");
}
sb.AppendLine(" default:");
sb.AppendLine(
" throw new NodeParameterNotFoundException(pipelineId, parameterName, nodeType, nodeId);");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
}
return sb.ToString();
}
private static string GetSpecificNodePortsMethodName(NodeDescriptor node)
{
return $"Get__{node.TypeNameShortSanitized}__NodePorts";
}
private static string BuildGetNodePortsMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(
" public Dictionary<NodePortKey, NodePortInfo> GetAllPorts(string pipelineId, HashSet<string> nodeTypes, Dictionary<string, IEnumerable<string>> nodeList)");
sb.AppendLine(" {");
sb.AppendLine(" var result = new Dictionary<NodePortKey, NodePortInfo>();");
sb.AppendLine(" foreach (var nodeType in nodeTypes)");
sb.AppendLine(" {");
sb.AppendLine(" if (!nodeList.TryGetValue(nodeType, out var ids)) continue;");
sb.AppendLine(" foreach (var nodeId in ids)");
sb.AppendLine(" {");
sb.AppendLine(" var ports = GetNodePorts(pipelineId, nodeType, nodeId);");
sb.AppendLine(" foreach (var kv in ports) result.Add(kv.Key, kv.Value);");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine(" return result;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
" public IEnumerable<NodePortInfo> GetNodeInputPorts(string pipelineId, string nodeType, string nodeId)");
sb.AppendLine(" {");
sb.AppendLine(" var ports = GetNodePorts(pipelineId, nodeType, nodeId);");
sb.AppendLine(" foreach (var kv in ports) if (kv.Key.Input) yield return kv.Value;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(
" private Dictionary<NodePortKey, NodePortInfo> GetNodePorts(string pipelineId, string nodeType, string nodeId)");
sb.AppendLine(" {");
sb.AppendLine(" switch (nodeType)");
sb.AppendLine(" {");
foreach (var n in model.Nodes)
sb.AppendLine(
$" {NodeGeneratorHelper.GetNodeNameCaseString(n)} return {GetSpecificNodePortsMethodName(n)}(nodeId);");
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeNotFoundException(pipelineId, nodeType, nodeId);");
sb.AppendLine(" }");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BoolToString(bool value)
{
return value.ToString().ToLower();
}
private static string BuildGetNodePortsSpecificMethods(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
foreach (var n in model.Nodes)
{
sb.AppendLine(
$" private static Dictionary<NodePortKey, NodePortInfo> {GetSpecificNodePortsMethodName(n)}(string nodeId)");
sb.AppendLine(" {");
sb.AppendLine(" return new Dictionary<NodePortKey, NodePortInfo>()");
sb.AppendLine(" {");
foreach (var f in n.Fields.Where(f => f.Direction is FieldDirection.Input or FieldDirection.Output))
{
var input = f.Direction == FieldDirection.Input ? "true" : "false";
sb.AppendLine(" {");
sb.AppendLine($" new NodePortKey(nodeId, \"{f.FieldName}\", {input}),");
sb.AppendLine(
$" new NodePortInfo(\"{f.FieldName}\", typeof({f.ValueType.TrimEnd('?')}), {BoolToString(f.IsNullableValueType)}, {BoolToString(f.Metadata?.IsRequired == true)}, {BoolToString(f.Metadata?.DisallowNullableOutput == true)}, {BoolToString(f.Direction == FieldDirection.Input)})");
sb.AppendLine(" },");
}
sb.AppendLine(" };");
sb.AppendLine(" }");
sb.AppendLine();
}
return sb.ToString();
}
private static string GetGetNodeOutputsSpecificMethodName(NodeDescriptor node)
{
return $"Get__{node.TypeNameShortSanitized}__NodeOutputs";
}
private static string BuildGetNodeOutputsMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(
" private IReadOnlyDictionary<string, object> GetNodeOutputs(string pipelineId, string nodeId, INode node)");
sb.AppendLine(" {");
sb.AppendLine(" switch (node)");
sb.AppendLine(" {");
var i = 1;
foreach (var n in model.Nodes)
{
sb.AppendLine($" case {n.TypeNameFull} t{i}:");
sb.AppendLine($" return {GetGetNodeOutputsSpecificMethodName(n)}(nodeId, t{i});");
i++;
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
return sb.ToString();
}
private static string BuildGetNodeOutputsSpecificMethods(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
foreach (var n in model.Nodes)
{
sb.AppendLine(
$" private IReadOnlyDictionary<string, object> {GetGetNodeOutputsSpecificMethodName(n)}(string nodeId, {n.TypeNameFull} node)");
sb.AppendLine(" {");
var nodeOutputs = n.Fields
.Where(f => f.Direction == FieldDirection.Output)
.ToList();
sb.Append(" return new Dictionary<string, object>");
if (nodeOutputs.Count == 0)
{
sb.AppendLine("();");
}
else
{
sb.AppendLine();
sb.AppendLine(" {");
foreach (var p in nodeOutputs)
sb.AppendLine(
$" {{ NodeFieldCodeGenerator.GenerateFieldCode(nodeId, \"{p.FieldName}\", FieldDirection.Output), node.{p.PropertyName} }},");
sb.AppendLine(" };");
}
sb.AppendLine(" }");
sb.AppendLine();
}
return sb.ToString();
}
private static string GetConnectPortsMethodName(NodeDescriptor node)
{
return $"Connect__{node.TypeNameShortSanitized}__NodePorts";
}
private static string BuildConnectPortsMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(
" public void ConnectPorts<TNode>(string pipelineId, TNode node, string nodeId, Dictionary<string, InputSource> inputs, IReadOnlyDictionary<string, INode> createdNodes) where TNode : INode");
sb.AppendLine(" {");
sb.AppendLine(" switch (node)");
sb.AppendLine(" {");
var i = 1;
foreach (var n in model.Nodes)
{
sb.AppendLine($" case {n.TypeNameFull} t{i}:");
sb.AppendLine(
$" {GetConnectPortsMethodName(n)}(pipelineId, t{i}, nodeId, inputs, createdNodes);");
sb.AppendLine(" break;");
i++;
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
sb.AppendLine(" }");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BuildConnectPortsSpecificMethods(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(
" private PortConnectionInfo? TryConnectPorts<T>(string pipelineId, string nodeId, string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs, string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool isRequired = false) where T : notnull");
sb.AppendLine(" {");
sb.AppendLine(" if (!inputs.TryGetValue(inputKey, out var connection))");
sb.AppendLine(
" return !isRequired ? null : throw new InputPortHasNoRequiredConnectionException(pipelineId, inputKey, nodeId, nodeType);");
sb.AppendLine(" var fromNodeId = connection.NodeId;");
sb.AppendLine(" var fromPortName = connection.OutputName;");
sb.AppendLine(
" if (nodeInput == null) throw new InputPortNotFoundException(pipelineId, inputKey, nodeId, nodeType);");
sb.AppendLine(
" if (!createdNodes.TryGetValue(fromNodeId, out var fromNode)) throw new OutputPortNodeNotFoundException(pipelineId, fromNodeId, nodeId);");
sb.AppendLine(" var outputs = GetNodeOutputs(pipelineId, fromNodeId, fromNode);");
sb.AppendLine(
" var outputFieldKey = NodeFieldCodeGenerator.GenerateFieldCode(fromNodeId, fromPortName, FieldDirection.Output);");
sb.AppendLine(
" if (!outputs.TryGetValue(outputFieldKey, out var outputFieldObj)) throw new OutputPortNotFoundException(pipelineId, fromPortName, fromNodeId, nodeId);");
sb.AppendLine(" var fromNodeType = GetNodeType(pipelineId, fromNodeId, fromNode);");
sb.AppendLine(" if (outputFieldObj is INodeField<T> outStrict && nodeInput is INodeField<T> inStrict)");
sb.AppendLine(" {");
sb.AppendLine(" PortConnector.ConnectStatic(outStrict, inStrict);");
sb.AppendLine(
" return new PortConnectionInfo(true, outputFieldObj, fromPortName, fromNodeType, fromNodeId);");
sb.AppendLine(" }");
sb.AppendLine(
" return new PortConnectionInfo(false, outputFieldObj, fromPortName, fromNodeType, fromNodeId);");
sb.AppendLine(" }");
sb.AppendLine();
foreach (var n in model.Nodes)
{
sb.AppendLine(
$" private void {GetConnectPortsMethodName(n)}(string pipelineId, {n.TypeNameFull} node, string nodeId, Dictionary<string, InputSource> inputs, IReadOnlyDictionary<string, INode> createdNodes)");
sb.AppendLine(" {");
foreach (var input in n.Fields.Where(f => f.Direction == FieldDirection.Input))
{
var methodName = input.IsValueReferenceType ? "ConnectPortsClass" : "ConnectPortsStruct";
sb.AppendLine(
$" {methodName}(pipelineId, nodeId, \"{n.TypeNameShort}\", node.{input.PropertyName}, nameof(node.{input.PropertyName}), inputs, \"{input.FieldName}\", createdNodes, disallowNullableOutput: {BoolToString(input.Metadata?.DisallowNullableOutput == true)}, isRequired: true);");
}
sb.AppendLine(" }");
sb.AppendLine();
}
return sb.ToString();
}
private static string BuildParseParameterMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(" private static T? ParseParameter<T>(string? valueString)");
sb.AppendLine(" {");
sb.AppendLine(" var type = typeof(T);");
sb.AppendLine(
" if (type == typeof(string)) return valueString is not null ? (T)(object)valueString : default;");
sb.AppendLine(" if (string.IsNullOrEmpty(valueString)) return default;");
sb.AppendLine(" if (type == typeof(int)) return (T)(object)int.Parse(valueString);");
sb.AppendLine(
" if (type == typeof(double)) return (T)(object)double.Parse(valueString, CultureInfo.InvariantCulture);");
sb.AppendLine(" if (type == typeof(bool)) return (T)(object)bool.Parse(valueString);");
var knownEnums = model.Nodes.SelectMany(q => q.Fields
.Where(x => x.Direction == FieldDirection.Parameter && x.IsValueEnum))
.Select(q => q.ValueType)
.Distinct();
foreach (var enumType in knownEnums)
sb.AppendLine(
$" if (type == typeof({enumType})) return (T)(object)Enum.Parse<{enumType}>(valueString);");
sb.AppendLine(" throw new NotSupportedException(type.Name);");
sb.AppendLine(" }");
return sb.ToString();
}
private static string GetGetParameterDefaultValueMethodName(NodeDescriptor node)
{
return $"GetParameterDefaultValueFor__{node.TypeNameShortSanitized}__Node";
}
private static string BuildGetParameterDefaultValueMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(" public object? GetParameterDefaultValue(string nodeType, string parameterName)");
sb.AppendLine(" {");
sb.AppendLine(" switch (nodeType)");
sb.AppendLine(" {");
foreach (var n in model.Nodes)
{
sb.AppendLine($" {NodeGeneratorHelper.GetNodeNameCaseString(n)}");
sb.AppendLine($" return {GetGetParameterDefaultValueMethodName(n)}(parameterName);");
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeParameterNotFoundException(parameterName, nodeType);");
sb.AppendLine(" }");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BuildGetParameterDefaultValueSpecificMethods(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
const string tab = " ";
foreach (var n in model.Nodes)
{
sb.AppendLine(
$" private static object? {GetGetParameterDefaultValueMethodName(n)}(string parameterName)");
sb.AppendLine(" {");
sb.AppendLine(" switch (parameterName)");
sb.AppendLine(" {");
foreach (var p in n.Fields.Where(f => f.Direction == FieldDirection.Parameter))
{
sb.AppendLine($" case \"{p.FieldName}\":");
var parameterType = string.Concat(p.ValueType, p.IsNullableValueType ? "?" : string.Empty);
sb.AppendLine(p.Metadata?.DefaultValue is null
? $" return GetDefaultValue<{parameterType}>();"
: GetDefaultValueCode(p, parameterType, tab));
}
sb.AppendLine(" default:");
sb.AppendLine($" throw new NodeParameterNotFoundException(parameterName, \"{n.Type}\");");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
}
sb.AppendLine(" private static T? GetDefaultValue<T>()");
sb.AppendLine(" {");
sb.AppendLine(" var type = typeof(T);");
sb.AppendLine(" if (type == typeof(string)) return (T)(object)string.Empty;");
sb.AppendLine(" if (type == typeof(int)) return (T)(object)0;");
sb.AppendLine(" if (type == typeof(double)) return (T)(object)0d;");
sb.AppendLine(" if (type == typeof(decimal)) return (T)(object)0m;");
sb.AppendLine(" if (type == typeof(bool)) return (T)(object)false;");
sb.AppendLine(" if (type == typeof(Guid)) return (T)(object)Guid.Empty;");
sb.AppendLine(" if (type.IsEnum) return default;");
sb.AppendLine(" throw new NotSupportedException(type.Name);");
sb.AppendLine(" }");
return sb.ToString();
}
private static string GetDefaultValueCode(NodeFieldDescriptor parameterDescriptor, string parameterType,
string tabs)
{
var sb = new StringBuilder();
if (parameterDescriptor.Metadata?.DefaultValue != null)
{
var defaultValue = parameterDescriptor.Metadata.DefaultValue;
sb.Append(tabs);
switch (defaultValue)
{
case string strVal:
sb.Append($"return \"{strVal}\";");
break;
case char c:
sb.Append($"return '{c}';");
break;
case bool b:
sb.Append($"return {b.ToString().ToLowerInvariant()};");
break;
case int or long or short or byte or uint or ulong or ushort or sbyte:
sb.Append($"return {defaultValue};");
break;
case float f:
sb.Append($"return {f}f;");
break;
case double d:
sb.Append($"return {d}d;");
break;
case decimal dec:
sb.Append($"return {dec}m;");
break;
case null:
sb.Append("return null;");
break;
default:
sb.Append(parameterDescriptor.Metadata?.CanDefaultValueBeInitialized == true
? $"return new {parameterType}();"
: $"throw new NotSupportedException(\"Cannot initialize default value for type {parameterType}\");");
break;
}
}
else
{
sb.Append($"return GetDefaultValue<{parameterType}>();");
}
return sb.ToString();
}
private static string GetSetNodeParametersValuesMethodName(NodeDescriptor node)
{
return $"Set__{node.TypeNameShortSanitized}__NodeParametersValues";
}
private static string BuildSetNodeParametersValuesMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(
" public void SetNodeParametersValues<TNode>(string pipelineId, TNode node, string nodeId, Dictionary<string, object?> parameters) where TNode : INode");
sb.AppendLine(" {");
sb.AppendLine(" switch (node)");
sb.AppendLine(" {");
var i = 1;
foreach (var n in model.Nodes)
{
sb.AppendLine($" case {n.TypeNameFull} t{i}:");
sb.AppendLine($" {GetSetNodeParametersValuesMethodName(n)}(t{i}, nodeId, parameters);");
sb.AppendLine(" break;");
i++;
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
sb.AppendLine(" }");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BuildSetNodeParametersValuesSpecificMethods(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
foreach (var n in model.Nodes)
{
sb.AppendLine(
$" private static void {GetSetNodeParametersValuesMethodName(n)}({n.TypeNameFull} node, string nodeId, Dictionary<string, object?> parameters)");
sb.AppendLine(" {");
var i = 1;
foreach (var p in n.Fields.Where(q => q.Direction == FieldDirection.Parameter))
{
sb.AppendLine(
$" if (parameters.TryGetValue(\"{p.FieldName}\", out var val{i}) && val{i} is {p.ValueType} typedVal{i})");
sb.AppendLine($" node.{p.PropertyName}.Value = typedVal{i};");
i++;
}
sb.AppendLine(" }");
sb.AppendLine();
}
return sb.ToString();
}
private static string BuildConnectPortsStructGenericMethod()
{
var sb = new StringBuilder();
sb.AppendLine(
" private void ConnectPortsStruct<T>(string pipelineId, string nodeId,string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs,");
sb.AppendLine(
" string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool disallowNullableOutput = false, bool isRequired = false)");
sb.AppendLine(" where T : struct");
sb.AppendLine(" {");
sb.AppendLine(
" var portConnectionInfo = TryConnectPorts(pipelineId, nodeId, nodeType, nodeInput, nodeInputName, inputs, inputKey, createdNodes, isRequired);");
sb.AppendLine(" if (portConnectionInfo is null || portConnectionInfo.Connected) return;");
sb.AppendLine(
" if (typeof(T).IsValueType && portConnectionInfo.OutputFieldObj is INodeField<T?> outputField)");
sb.AppendLine(" {");
sb.AppendLine(" PortConnector.ConnectRuntimeFromNullableStruct(outputField, nodeInput!,");
sb.AppendLine(
" nodeId, inputKey, portConnectionInfo.FromNodeId, portConnectionInfo.FromPortName);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine(
" if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNullable && nodeInput is INodeField<T?> inNullable)");
sb.AppendLine(" {");
sb.AppendLine(" PortConnector.ConnectRuntimeBothNullable(outNullable, inNullable);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine(
" if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNToStrict && nodeInput != null)");
sb.AppendLine(" {");
sb.AppendLine(" if (disallowNullableOutput)");
sb.AppendLine(" {");
sb.AppendLine(
" throw new NonNullableInputRestrictsConnectionToNullableOutputException(pipelineId, nodeId, nodeType, inputKey, portConnectionInfo.FromPortName, portConnectionInfo.FromNodeId);");
sb.AppendLine(" }");
sb.AppendLine(
" PortConnector.ConnectRuntimeFromNullableStruct(outNToStrict, nodeInput, currentNodeId: nodeId,");
sb.AppendLine(
" inputPortName: inputKey, sourceNodeId: portConnectionInfo.FromNodeId, outputPortName: portConnectionInfo.FromPortName);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine(
" if (portConnectionInfo.OutputFieldObj is INodeField<T> outStrictToN && nodeInput is INodeField<T?> inNullable2)");
sb.AppendLine(" {");
sb.AppendLine(" PortConnector.ConnectRuntimeToNullableStruct(outStrictToN, inNullable2);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine(" throw new PortsTypeMismatchException(pipelineId, inputKey, nodeType, nodeId,");
sb.AppendLine(
" portConnectionInfo.FromPortName, portConnectionInfo.FromNodeType, portConnectionInfo.FromNodeId);");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BuildConnectPortsClassGenericMethod()
{
var sb = new StringBuilder();
sb.AppendLine(
" private void ConnectPortsClass<T>(string pipelineId, string nodeId,string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs,");
sb.AppendLine(
" string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool disallowNullableOutput = false, bool isRequired = false)");
sb.AppendLine(" where T : class");
sb.AppendLine(" {");
sb.AppendLine(
" var portConnectionInfo = TryConnectPorts(pipelineId, nodeId, nodeType, nodeInput, nodeInputName, inputs, inputKey, createdNodes, isRequired);");
sb.AppendLine(" if (portConnectionInfo is null || portConnectionInfo.Connected) return;");
sb.AppendLine(
" if (!typeof(T).IsValueType && portConnectionInfo.OutputFieldObj is INodeField<T?> outputField)");
sb.AppendLine(" {");
sb.AppendLine(" PortConnector.ConnectRuntimeFromNullableClass(outputField, nodeInput!,");
sb.AppendLine(
" nodeId, inputKey, portConnectionInfo.FromNodeId, portConnectionInfo.FromPortName);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine(
" if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNullable && nodeInput is INodeField<T?> inNullable)");
sb.AppendLine(" {");
sb.AppendLine(" PortConnector.ConnectRuntimeBothNullable(outNullable, (INodeField<T?>)inNullable);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine(
" if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNToStrict && nodeInput != null)");
sb.AppendLine(" {");
sb.AppendLine(" if (disallowNullableOutput)");
sb.AppendLine(" {");
sb.AppendLine(
" throw new NonNullableInputRestrictsConnectionToNullableOutputException(pipelineId, nodeId, nodeType, inputKey, portConnectionInfo.FromPortName, portConnectionInfo.FromNodeId);");
sb.AppendLine(" }");
sb.AppendLine(
" PortConnector.ConnectRuntimeFromNullableClass(outNToStrict, nodeInput, currentNodeId: nodeId,");
sb.AppendLine(
" inputPortName: inputKey, sourceNodeId: portConnectionInfo.FromNodeId, outputPortName: portConnectionInfo.FromPortName);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine(
" if (portConnectionInfo.OutputFieldObj is INodeField<T> outStrictToN && nodeInput is INodeField<T?> inNullable2)");
sb.AppendLine(" {");
sb.AppendLine(
" PortConnector.ConnectRuntimeToNullableClass(outStrictToN, (INodeField<T?>)inNullable2);");
sb.AppendLine(" return;");
sb.AppendLine(" }");
sb.AppendLine(" throw new PortsTypeMismatchException(pipelineId, inputKey, nodeType, nodeId,");
sb.AppendLine(
" portConnectionInfo.FromPortName, portConnectionInfo.FromNodeType, portConnectionInfo.FromNodeId);");
sb.AppendLine(" }");
return sb.ToString();
}
private static string BuildGetNodeTypeMethod(NodeModelBuilder.NodesModel model)
{
var sb = new StringBuilder();
sb.AppendLine(" private static string GetNodeType(string pipelineId, string nodeId, INode node)");
sb.AppendLine(" {");
sb.AppendLine(" switch (node)");
sb.AppendLine(" {");
foreach (var n in model.Nodes)
{
var nodeName = NodeGeneratorHelper.GetNodeName(n);
sb.AppendLine($" case {n.TypeNameFull}:");
sb.AppendLine($" return {nodeName};");
}
sb.AppendLine(" default:");
sb.AppendLine(" throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
return sb.ToString();
}
}