Esempio n. 1
0
        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);
        }
Esempio n. 2
0
 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);
        }