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> 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 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 {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 GetAllPorts(string pipelineId, HashSet nodeTypes, Dictionary> nodeList)"); sb.AppendLine(" {"); sb.AppendLine(" var result = new Dictionary();"); 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 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 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 {GetSpecificNodePortsMethodName(n)}(string nodeId)"); sb.AppendLine(" {"); sb.AppendLine(" return new Dictionary()"); 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 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 {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"); 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(string pipelineId, TNode node, string nodeId, Dictionary inputs, IReadOnlyDictionary 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(string pipelineId, string nodeId, string nodeType, INodeField? nodeInput, string nodeInputName, Dictionary inputs, string inputKey, IReadOnlyDictionary 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 outStrict && nodeInput is INodeField 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 inputs, IReadOnlyDictionary 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(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()"); 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(string pipelineId, TNode node, string nodeId, Dictionary 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 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(string pipelineId, string nodeId,string nodeType, INodeField? nodeInput, string nodeInputName, Dictionary inputs,"); sb.AppendLine( " string inputKey, IReadOnlyDictionary 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 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 outNullable && nodeInput is INodeField inNullable)"); sb.AppendLine(" {"); sb.AppendLine(" PortConnector.ConnectRuntimeBothNullable(outNullable, inNullable);"); sb.AppendLine(" return;"); sb.AppendLine(" }"); sb.AppendLine( " if (portConnectionInfo.OutputFieldObj is INodeField 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 outStrictToN && nodeInput is INodeField 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(string pipelineId, string nodeId,string nodeType, INodeField? nodeInput, string nodeInputName, Dictionary inputs,"); sb.AppendLine( " string inputKey, IReadOnlyDictionary 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 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 outNullable && nodeInput is INodeField inNullable)"); sb.AppendLine(" {"); sb.AppendLine(" PortConnector.ConnectRuntimeBothNullable(outNullable, (INodeField)inNullable);"); sb.AppendLine(" return;"); sb.AppendLine(" }"); sb.AppendLine( " if (portConnectionInfo.OutputFieldObj is INodeField 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 outStrictToN && nodeInput is INodeField inNullable2)"); sb.AppendLine(" {"); sb.AppendLine( " PortConnector.ConnectRuntimeToNullableClass(outStrictToN, (INodeField)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(); } }