CSCodeGenerationResult DoImplement(IActivityMonitor monitor, Type classType, ICSCodeGenerationContext c, ITypeScope scope, IPocoSupportResult poco, Setup.Json.JsonSerializationCodeGen?json = null) { if (classType != typeof(CrisCommandDirectoryLike)) { throw new InvalidOperationException("Applies only to the CrisCommandDirectoryLike class."); } // In real Cris, there is a CommandRegistry that registers the commands/handlers/service etc. into an intermediate Entry descriptor // with the final (most specific) TResult. // Here we shortcut the process and work with the basic IPocoRootInfo: if (!poco.OtherInterfaces.TryGetValue(typeof(ICommand), out IReadOnlyList <IPocoRootInfo>?commandPocos)) { commandPocos = Array.Empty <IPocoRootInfo>(); } CodeWriterExtensions.Append(scope, "public ").Append(scope.Name).Append("() : base( CreateCommands() ) {}").NewLine(); scope.Append("static IReadOnlyList<ICommandModel> CreateCommands()").NewLine() .OpenBlock() .Append("var list = new ICommandModel[]").NewLine() .Append("{").NewLine(); int idx = 0; foreach (var e in commandPocos) { var f = c.Assembly.FindOrCreateAutoImplementedClass(monitor, e.PocoFactoryClass); f.Definition.BaseTypes.Add(new ExtendedTypeName("ICommandModel")); f.Append("public Type CommandType => PocoClassType;").NewLine() .Append("public int CommandIdx => ").Append(idx++).Append(";").NewLine() .Append("public string CommandName => Name;").NewLine() .Append("ICommand ICommandModel.Create() => (ICommand)Create();").NewLine(); // The CommandModel is the _factory field. var p = c.Assembly.FindOrCreateAutoImplementedClass(monitor, e.PocoClass); p.Append("public ICommandModel CommandModel => _factory;").NewLine(); scope.Append(p.FullName).Append("._factory,").NewLine(); } scope.Append("};").NewLine() .Append("return list;") .CloseBlock(); return(CSCodeGenerationResult.Success); }
public static void GenerateAutoInstantiatedNewAssignation(this IPocoSupportResult @this, ICodeWriter writer, string variableName, Type autoType) { writer.Append(variableName).Append(" = "); if (@this.AllInterfaces.TryGetValue(autoType, out IPocoInterfaceInfo? info)) { writer.Append("new ").Append(info.Root.PocoClass.FullName !).Append("();").NewLine(); return; } if (@this.PocoClass.ByType.ContainsKey(autoType)) { writer.Append("new ").AppendCSharpName(autoType, true, true, true).Append("();").NewLine(); return; } if (autoType.IsGenericType) { Type genType = autoType.GetGenericTypeDefinition(); if (genType == typeof(List <>)) { writer.Append("new List<").AppendCSharpName(autoType.GetGenericArguments()[0], true, true, true).Append(">();").NewLine(); return; } if (genType == typeof(Dictionary <,>)) { writer.Append("new Dictionary<") .AppendCSharpName(autoType.GetGenericArguments()[0], true, true, true) .Append(',') .AppendCSharpName(autoType.GetGenericArguments()[1], true, true, true) .Append(">();") .NewLine(); return; } if (genType == typeof(HashSet <>)) { writer.Append("new HashSet<").AppendCSharpName(autoType.GetGenericArguments()[0], true, true, true).Append(">();").NewLine(); return; } } Throw.ArgumentException($"Invalid type '{autoType.FullName}': readonly properties can only be IPoco (that are not marked with [CKTypeDefiner] or [CKTypeSuperDefiner]), Poco objects (marked with a [PocoClass] attribute), HashSet<>, List<>, or Dictionary<,>.", nameof(autoType)); }
CSCodeGenerationResult AllowAllPocoTypes(IActivityMonitor monitor, ICSCodeGenerationContext c, IPocoSupportResult pocoSupport, JsonSerializationCodeGen jsonCodeGen) { if (pocoSupport.Roots.Count == 0) { monitor.Info("No Poco available. Skipping Poco serialization code generation."); return(new CSCodeGenerationResult(nameof(FinalizeJsonSupport))); } using (monitor.OpenInfo($"Allowing Poco Json serialization C# code generation for {pocoSupport.Roots.Count} Pocos.")) { // IPoco and IClosedPoco are not in the "OtherInterfaces". jsonCodeGen.AllowInterfaceToUntyped(typeof(IPoco)); jsonCodeGen.AllowInterfaceToUntyped(typeof(IClosedPoco)); // Maps the "other Poco interfaces" to "Untyped" object. foreach (var other in pocoSupport.OtherInterfaces) { jsonCodeGen.AllowInterfaceToUntyped(other.Key); } bool success = true; // Registers TypeInfo for the PocoClass and maps its interfaces to the PocoClass. foreach (var root in pocoSupport.Roots) { var typeInfo = jsonCodeGen.AllowTypeInfo(root.PocoClass, root.Name, root.PreviousNames)?.Configure( (ICodeWriter write, string variableName) => write.Append("((PocoJsonSerializer.IWriter)").Append(variableName).Append(").Write( w, false, options );"), (ICodeWriter read, string variableName, bool assignOnly, bool isNullable) => { if (!assignOnly) { if (isNullable) { read.Append("if( ").Append(variableName).Append(" != null ) ").NewLine(); } read.Append("((").AppendCSharpName(root.PocoClass, true, true, true).Append(')').Append(variableName).Append(')').Append(".Read( ref r, options );"); if (isNullable) { read.NewLine().Append("else").NewLine(); assignOnly = true; } } if (assignOnly) { read.Append(variableName) .Append(" = new ").AppendCSharpName(root.PocoClass, true, true, true) .Append("( ref r, options );"); } }); if (typeInfo != null) { foreach (var i in root.Interfaces) { jsonCodeGen.AllowTypeAlias(i.PocoInterface.GetNullableTypeTree(), typeInfo); } } else { success = false; } } return(success ? new CSCodeGenerationResult(nameof(GeneratePocoSupport)) : CSCodeGenerationResult.Failed); } }
CSCodeGenerationResult GeneratePocoSupport(IActivityMonitor monitor, ICSCodeGenerationContext c, IPocoSupportResult pocoSupport, JsonSerializationCodeGen jsonCodeGen) { using var g = monitor.OpenInfo($"Generating C# Json serialization code."); bool success = true; // Generates the factory and the Poco class code. foreach (var root in pocoSupport.Roots) { var factory = c.Assembly.FindOrCreateAutoImplementedClass(monitor, root.PocoFactoryClass); foreach (var i in root.Interfaces) { var interfaceName = i.PocoInterface.ToCSharpName(); var readerName = "PocoJsonSerializer.IFactoryReader<" + interfaceName + ">"; factory.Definition.BaseTypes.Add(new ExtendedTypeName(readerName)); factory.Append(interfaceName).Append(' ').Append(readerName).Append(".Read( ref System.Text.Json.Utf8JsonReader r, PocoJsonSerializerOptions options )").NewLine() .Append(" => r.TokenType == System.Text.Json.JsonTokenType.Null ? null : new ") .Append(root.PocoClass.Name).Append("( ref r, options );").NewLine(); } factory.Append("public IPoco ReadTyped( ref System.Text.Json.Utf8JsonReader r, PocoJsonSerializerOptions options ) => new ").Append(root.PocoClass.Name).Append("( ref r, options );").NewLine(); var pocoClass = c.Assembly.FindOrCreateAutoImplementedClass(monitor, root.PocoClass); // Generates the Poco class Read and Write methods. // UnionTypes on properties are registered. success &= ExtendPocoClass(monitor, root, jsonCodeGen, pocoClass); } return(success ? new CSCodeGenerationResult(nameof(FinalizeJsonSupport)) : CSCodeGenerationResult.Failed); }
/// <summary> /// Generates the <paramref name="scope"/> that is the PocoDirectory_CK class and /// all the factories (<see cref="IPocoFactory"/> implementations) and the Poco class (<see cref="IPoco"/> implementations). /// </summary> /// <param name="monitor">The monitor to use.</param> /// <param name="classType">The <see cref="PocoDirectory"/> type.</param> /// <param name="c">Code generation context.</param> /// <param name="scope">The PocoDirectory_CK type scope.</param> /// <returns>Always <see cref="CSCodeGenerationResult.Success"/>.</returns> public override CSCodeGenerationResult Implement(IActivityMonitor monitor, Type classType, ICSCodeGenerationContext c, ITypeScope scope) { Debug.Assert(scope.FullName == "CK.Core.PocoDirectory_CK", "We can use the PocoDirectory_CK type name to reference the PocoDirectory implementation."); IPocoSupportResult r = c.Assembly.GetPocoSupportResult(); Debug.Assert(r == c.CurrentRun.ServiceContainer.GetService(typeof(IPocoSupportResult)), "The PocoSupportResult is also available at the GeneratedBinPath."); // PocoDirectory_CK class. scope.GeneratedByComment().NewLine() .FindOrCreateFunction("internal PocoDirectory_CK()") .Append("Instance = this;").NewLine(); scope.Append("internal static PocoDirectory_CK Instance;").NewLine() // The _factories field .Append("static readonly Dictionary<string,IPocoFactory> _factoriesN = new Dictionary<string,IPocoFactory>( ").Append(r.NamedRoots.Count).Append(" );").NewLine() .Append("static readonly Dictionary<Type,IPocoFactory> _factoriesT = new Dictionary<Type,IPocoFactory>( ").Append(r.AllInterfaces.Count).Append(" );").NewLine() .Append("public override IPocoFactory Find( string name ) => _factoriesN.GetValueOrDefault( name );").NewLine() .Append("public override IPocoFactory Find( Type t ) => _factoriesT.GetValueOrDefault( t );").NewLine() .Append("internal static void Register( IPocoFactory f )").OpenBlock() .Append("_factoriesN.Add( f.Name, f );").NewLine() .Append("foreach( var n in f.PreviousNames ) _factoriesN.Add( n, f );").NewLine() .Append("foreach( var i in f.Interfaces ) _factoriesT.Add( i, f );").NewLine() .CloseBlock(); if (r.AllInterfaces.Count == 0) { return(CSCodeGenerationResult.Success); } foreach (var root in r.Roots) { // PocoFactory class. var tFB = c.Assembly.FindOrCreateAutoImplementedClass(monitor, root.PocoFactoryClass); tFB.Definition.Modifiers |= Modifiers.Sealed; string factoryClassName = tFB.Definition.Name.Name; // Poco class. var tB = c.Assembly.FindOrCreateAutoImplementedClass(monitor, root.PocoClass); tB.Definition.Modifiers |= Modifiers.Sealed; // The Poco's static _factory field is internal and its type is the exact class: extended code // can refer to the _factory to access the factory extended code without cast. // // This static internal field is an awful shortcut but it makes things simpler and more efficient // than looking up the factory in the DI (and downcasting it) each time we need it. // This simplification has been done for Cris Command implementation: a ICommand exposes // its ICommandModel: we used to inject the ICommandModel (that is the extended PocoFactory) in the ICommand // PocoClass ctor from the factory methods. It worked but it was complex... and, eventually, there // can (today) but most importantly there SHOULD, be only one StObjMap/Concrete generated code in an // assembly. Maybe one day, the StObj instances themselves can be made static (since they are some kind of // "absolute singletons"). // // Note to myself: this "static shortcut" is valid because we are on a "final generation", not on a // local, per-module, intermediate, code generation like .Net 5 Code Generators. // How this kind of shortcuts could be implemented with .Net 5 Code Generators? It seems that it could but // there will be as many "intermediate statics" as there are "levels of assemblies"? Or, there will be only // one static (the first one) and the instance will be replaced by the subsequent assemblies? In all cases, // diamond problem will have to be ultimately resolved at the final leaf... Just like we do! // tB.Append("internal static ").Append(tFB.Name).Append(" _factory;") .NewLine(); tB.Append("IPocoFactory IPocoGeneratedClass.Factory => _factory;").NewLine(); // Always create the constructor so that other code generators // can always find it. // We support the interfaces here: if other participants have already created this type, it is // up to us, here, to handle the "exact" type definition. tB.Definition.BaseTypes.Add(new ExtendedTypeName("IPocoGeneratedClass")); tB.Definition.BaseTypes.AddRange(root.Interfaces.Select(i => new ExtendedTypeName(i.PocoInterface.ToCSharpName()))); IFunctionScope ctorB = tB.CreateFunction($"public {root.PocoClass.Name}()"); foreach (var p in root.PropertyList) { Type propType = p.PropertyType; bool isUnionType = p.PropertyUnionTypes.Any(); var typeName = propType.ToCSharpName(); string fieldName = "_v" + p.Index; tB.Append(typeName).Space().Append(fieldName); if (p.DefaultValueSource == null) { tB.Append(";"); } else { tB.Append(" = ").Append(p.DefaultValueSource).Append(";"); } tB.NewLine(); tB.Append("public ").Append(typeName).Space().Append(p.PropertyName); Debug.Assert(!p.IsReadOnly || p.DefaultValueSource == null, "Readonly with [DefaultValue] has already raised an error."); if (p.IsReadOnly) { // Generates in constructor. r.GenerateAutoInstantiatedNewAssignation(ctorB, fieldName, p.PropertyType); } tB.OpenBlock() .Append("get => ").Append(fieldName).Append(";").NewLine(); if (!p.IsReadOnly) { tB.Append("set") .OpenBlock(); bool isTechnicallyNullable = p.PropertyNullableTypeTree.Kind.IsTechnicallyNullable(); bool isNullable = p.PropertyNullableTypeTree.Kind.IsNullable(); if (isTechnicallyNullable && !isNullable) { tB.Append("if( value == null ) throw new ArgumentNullException();").NewLine(); } if (isUnionType) { if (isNullable) { tB.Append("if( value != null )") .OpenBlock(); } tB.Append("Type tV = value.GetType();").NewLine() .Append("if( !_c").Append(fieldName) .Append(".Any( t => t.IsAssignableFrom( tV ) ))") .OpenBlock() .Append("throw new ArgumentException( $\"Unexpected Type '{tV}' in UnionType. Allowed types are: ") .Append(p.PropertyUnionTypes.Select(tU => tU.ToString()).Concatenate()) .Append(".\");") .CloseBlock(); if (isNullable) { tB.CloseBlock(); } } tB.Append(fieldName).Append(" = value;") .CloseBlock(); } tB.CloseBlock(); if (isUnionType) { tB.Append("static readonly Type[] _c").Append(fieldName).Append("=").AppendArray(p.PropertyUnionTypes.Select(u => u.Type)).Append(";").NewLine(); } } // PocoFactory class. tFB.Append("PocoDirectory IPocoFactory.PocoDirectory => PocoDirectory_CK.Instance;").NewLine(); tFB.Append("public Type PocoClassType => typeof(").Append(root.PocoClass.Name).Append(");") .NewLine(); tFB.Append("public IPoco Create() => new ").Append(root.PocoClass.Name).Append("();") .NewLine(); tFB.Append("public string Name => ").AppendSourceString(root.Name).Append(";") .NewLine(); tFB.Append("public IReadOnlyList<string> PreviousNames => ").AppendArray(root.PreviousNames).Append(";") .NewLine(); tFB.Append("public IReadOnlyList<Type> Interfaces => ").AppendArray(root.Interfaces.Select(i => i.PocoInterface)).Append(";") .NewLine(); tFB.CreateFunction("public " + factoryClassName + "()") .Append("PocoDirectory_CK.Register( this );").NewLine() .Append(tB.Name).Append("._factory = this;"); foreach (var i in root.Interfaces) { tFB.Definition.BaseTypes.Add(new ExtendedTypeName(i.PocoFactoryInterface.ToCSharpName())); tFB.AppendCSharpName(i.PocoInterface, true, true, true) .Space() .AppendCSharpName(i.PocoFactoryInterface, true, true, true) .Append(".Create() => new ").AppendCSharpName(i.Root.PocoClass, true, true, true).Append("();") .NewLine(); } } return(CSCodeGenerationResult.Success); }