优化代码生成,支持部分表生成模型类和接口
大石头 编写于 2022-02-16 18:33:12
X
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NewLife;
using NewLife.Collections;
using NewLife.Log;
using NewLife.Reflection;
using NewLife.Serialization;
using XCode.DataAccessLayer;

namespace XCode.Code
{
    /// <summary>类代码生成器</summary>
    public class ClassBuilder
    {
        #region 属性
        /// <summary>写入器</summary>
        public TextWriter Writer { get; set; }

        /// <summary>数据表</summary>
        public IDataTable Table { get; set; }

        /// <summary>类名。默认Table.Name</summary>
        public String ClassName { get; set; }

        /// <summary>生成器选项</summary>
        public BuilderOption Option { get; set; } = new BuilderOption();
        #endregion

        #region 静态快速
        /// <summary>加载模型文件</summary>
        /// <param name="xmlFile">Xml模型文件</param>
        /// <param name="option">生成可选项</param>
        /// <param name="atts">扩展属性字典</param>
        /// <returns></returns>
        public static IList<IDataTable> LoadModels(String xmlFile, BuilderOption option, out IDictionary<String, String> atts)
        {
            if (xmlFile.IsNullOrEmpty())
            {
                var di = ".".GetBasePath().AsDirectory();
                //XTrace.WriteLine("未指定模型文件,准备从目录中查找第一个xml文件 {0}", di.FullName);
                // 选当前目录第一个
                xmlFile = di.GetFiles("*.xml", SearchOption.TopDirectoryOnly).FirstOrDefault()?.FullName;
            }

            if (xmlFile.IsNullOrEmpty()) throw new Exception("找不到任何模型文件!");

            xmlFile = xmlFile.GetBasePath();
            if (!File.Exists(xmlFile)) throw new FileNotFoundException("指定模型文件不存在!", xmlFile);

            // 导入模型
            var xmlContent = File.ReadAllText(xmlFile);
            atts = new NullableDictionary<String, String>(StringComparer.OrdinalIgnoreCase)
            {
                ["xmlns"] = "http://www.newlifex.com/Model2022.xsd",
                ["xmlns:xs"] = "http://www.w3.org/2001/XMLSchema-instance",
                ["xs:schemaLocation"] = "http://www.newlifex.com http://www.newlifex.com/Model2022.xsd"
            };

            if (Debug) XTrace.WriteLine("导入模型:{0}", xmlFile);

            // 导入模型
            var tables = ModelHelper.FromXml(xmlContent, DAL.CreateTable, atts);

            if (option != null)
            {
                option.Output = atts["Output"] ?? Path.GetDirectoryName(xmlFile);
                option.Namespace = atts["NameSpace"] ?? Path.GetFileNameWithoutExtension(xmlFile);
                option.ConnName = atts["ConnName"];
                option.BaseClass = atts["BaseClass"];
            }

            // 保存文件名
            atts["ModelFile"] = xmlFile;

            return tables;
        }

        /// <summary>生成简易版模型</summary>
        /// <param name="tables">表集合</param>
        /// <param name="option">可选项</param>
        /// <returns></returns>
        public static Int32 BuildModels(IList<IDataTable> tables, BuilderOption option = null)
        {
            if (option == null)
                option = new BuilderOption();
            else
                option = option.Clone();

            option.Pure = true;
            option.Partial = true;

            if (Debug) XTrace.WriteLine("生成简易模型类 {0}", option.Output.GetBasePath());

            var count = 0;
            foreach (var item in tables)
            {
                // 跳过排除项
                if (option.Excludes.Contains(item.Name)) continue;
                if (option.Excludes.Contains(item.TableName)) continue;

                var builder = new ClassBuilder
                {
                    Table = item,
                    Option = option.Clone(),
                };
                if (Debug) builder.Log = XTrace.Log;

                builder.Load(item);

                // 自定义模型
                var modelClass = item.Properties["ModelClass"];
                var modelInterface = item.Properties["ModelInterface"];
                if (!modelClass.IsNullOrEmpty()) builder.Option.ClassNameTemplate = modelClass;
                if (!modelInterface.IsNullOrEmpty())
                {
                    builder.Option.BaseClass = modelInterface;
                    builder.Option.ModelNameForCopy = modelInterface;
                }

                builder.Execute();
                builder.Save(null, true, false);

                count++;
            }

            return count;
        }

        /// <summary>生成简易版实体接口</summary>
        /// <param name="tables">表集合</param>
        /// <param name="option">可选项</param>
        /// <returns></returns>
        public static Int32 BuildInterfaces(IList<IDataTable> tables, BuilderOption option = null)
        {
            if (option == null)
                option = new BuilderOption();
            else
                option = option.Clone();

            option.Interface = true;
            option.Partial = true;

            if (Debug) XTrace.WriteLine("生成简易接口 {0}", option.Output.GetBasePath());

            var count = 0;
            foreach (var item in tables)
            {
                // 跳过排除项
                if (option.Excludes.Contains(item.Name)) continue;
                if (option.Excludes.Contains(item.TableName)) continue;

                var builder = new ClassBuilder
                {
                    Table = item,
                    Option = option.Clone(),
                };
                if (Debug) builder.Log = XTrace.Log;

                builder.Load(item);

                // 自定义模型
                var modelInterface = item.Properties["ModelInterface"];
                if (!modelInterface.IsNullOrEmpty()) builder.Option.ClassNameTemplate = modelInterface;

                builder.Execute();
                builder.Save(null, true, false);

                count++;
            }

            return count;
        }
        #endregion

        #region 方法
        /// <summary>加载数据表</summary>
        /// <param name="table"></param>
        public virtual void Load(IDataTable table)
        {
            Table = table;

            var option = Option;

            // 命名空间
            var str = table.Properties["Namespace"];
            if (!str.IsNullOrEmpty()) option.Namespace = str;

            // 输出目录
            str = table.Properties["Output"];
            if (!str.IsNullOrEmpty()) option.Output = str.GetBasePath();
        }
        #endregion

        #region 主方法
        /// <summary>执行生成</summary>
        public virtual void Execute()
        {
            var option = Option;
            if (ClassName.IsNullOrEmpty())
            {
                if (!option.ClassNameTemplate.IsNullOrEmpty())
                    ClassName = option.ClassNameTemplate.Replace("{name}", Table.Name);
                else
                    ClassName = option.Interface ? ("I" + Table.Name) : Table.Name;
            }
            WriteLog("生成 {0} {1} {2}", Table.Name, Table.DisplayName, new { option.ClassNameTemplate, option.BaseClass, option.ModelNameForCopy, option.Namespace }.ToJson(false, false, false));

            Clear();
            if (Writer == null) Writer = new StringWriter();

            OnExecuting();

            BuildItems();

            OnExecuted();
        }

        /// <summary>生成头部</summary>
        protected virtual void OnExecuting()
        {
            // 引用命名空间
            var us = Option.Usings;
            if (Option.Extend && !us.Contains("NewLife.Data")) us.Add("NewLife.Data");

            us = us.Distinct().OrderBy(e => e.StartsWith("System") ? 0 : 1).ThenBy(e => e).ToArray();
            foreach (var item in us)
            {
                WriteLine("using {0};", item);
            }
            WriteLine();

            var ns = Option.Namespace;
            if (!ns.IsNullOrEmpty())
            {
                WriteLine("namespace {0}", ns);
                WriteLine("{");
            }

            BuildClassHeader();
        }

        /// <summary>实体类头部</summary>
        protected virtual void BuildClassHeader()
        {
            // 头部
            BuildAttribute();

            // 基类
            var baseClass = GetBaseClass();
            if (!baseClass.IsNullOrEmpty()) baseClass = " : " + baseClass;

            // 分部类
            var partialClass = Option.Partial ? " partial" : "";

            // 类接口
            if (Option.Interface)
                WriteLine("public{2} interface {0}{1}", ClassName, baseClass, partialClass);
            else
                WriteLine("public{2} class {0}{1}", ClassName, baseClass, partialClass);
            WriteLine("{");
        }

        /// <summary>获取基类</summary>
        /// <returns></returns>
        protected virtual String GetBaseClass()
        {
            var baseClass = Option.BaseClass?.Replace("{name}", Table.Name);
            if (Option.Extend)
            {
                if (!baseClass.IsNullOrEmpty()) baseClass += ", ";
                baseClass += "IExtend";
            }

            return baseClass;
        }

        /// <summary>实体类头部</summary>
        protected virtual void BuildAttribute()
        {
            // 注释
            var des = Table.Description;
            if (!Option.DisplayNameTemplate.IsNullOrEmpty())
            {
                des = Table.Description.TrimStart(Table.DisplayName, "。");
                des = Option.DisplayNameTemplate.Replace("{displayName}", Table.DisplayName) + "。" + des;
            }
            WriteLine("/// <summary>{0}</summary>", des);

            if (!Option.Pure && !Option.Interface)
            {
                WriteLine("[Serializable]");
                WriteLine("[DataObject]");

                if (!des.IsNullOrEmpty()) WriteLine("[Description(\"{0}\")]", des);
            }
        }

        /// <summary>生成尾部</summary>
        protected virtual void OnExecuted()
        {
            // 类接口
            WriteLine("}");

            if (!Option.Namespace.IsNullOrEmpty())
            {
                Writer.Write("}");
            }
        }

        /// <summary>生成主体</summary>
        protected virtual void BuildItems()
        {
            WriteLine("#region 属性");
            for (var i = 0; i < Table.Columns.Count; i++)
            {
                var column = Table.Columns[i];

                // 跳过排除项
                if (!ValidColumn(column)) continue;

                if (i > 0) WriteLine();
                BuildItem(column);
            }
            WriteLine("#endregion");

            if (Option.Extend)
            {
                WriteLine();
                BuildExtend();
            }

            // 生成拷贝函数。需要有基类
            //var bs = Option.BaseClass.Split(",").Select(e => e.Trim()).ToArray();
            //var model = bs.FirstOrDefault(e => e[0] == 'I' && e.Contains("{name}"));
            var model = Option.ModelNameForCopy;
            if (!Option.Interface && !model.IsNullOrEmpty())
            {
                WriteLine();
                BuildCopy(model.Replace("{name}", Table.Name));
            }
        }

        /// <summary>生成每一项</summary>
        protected virtual void BuildItem(IDataColumn column)
        {
            var dc = column;
            //BuildItemAttribute(column);
            // 注释
            var des = dc.Description;
            WriteLine("/// <summary>{0}</summary>", des);

            if (!Option.Pure && !Option.Interface)
            {
                if (!des.IsNullOrEmpty()) WriteLine("[Description(\"{0}\")]", des);

                var dis = dc.DisplayName;
                if (!dis.IsNullOrEmpty()) WriteLine("[DisplayName(\"{0}\")]", dis);
            }

            var type = dc.Properties["Type"];
            if (type.IsNullOrEmpty()) type = dc.DataType?.Name;

            if (Option.Interface)
                WriteLine("{0} {1} {{ get; set; }}", type, dc.Name);
            else
                WriteLine("public {0} {1} {{ get; set; }}", type, dc.Name);
        }

        private void BuildExtend()
        {
            WriteLine("#region 获取/设置 字段值");
            WriteLine("/// <summary>获取/设置 字段值</summary>");
            WriteLine("/// <param name=\"name\">字段名</param>");
            WriteLine("/// <returns></returns>");
            WriteLine("public virtual Object this[String name]");
            WriteLine("{");

            // get
            WriteLine("get");
            WriteLine("{");
            {
                WriteLine("switch (name)");
                WriteLine("{");
                foreach (var column in Table.Columns)
                {
                    // 跳过排除项
                    if (!ValidColumn(column)) continue;

                    WriteLine("case \"{0}\": return {0};", column.Name);
                }
                WriteLine("default: throw new KeyNotFoundException($\"{name} not found\");");
                WriteLine("}");
            }
            WriteLine("}");

            // set
            WriteLine("set");
            WriteLine("{");
            {
                WriteLine("switch (name)");
                WriteLine("{");
                var conv = typeof(Convert);
                foreach (var column in Table.Columns)
                {
                    // 跳过排除项
                    if (!ValidColumn(column)) continue;

                    var type = column.Properties["Type"];
                    if (type.IsNullOrEmpty()) type = column.DataType?.Name;

                    if (!type.IsNullOrEmpty())
                    {
                        if (!type.Contains("."))
                        {

                        }
                        if (!type.Contains(".") && conv.GetMethod("To" + type, new Type[] { typeof(Object) }) != null)
                        {
                            switch (type)
                            {
                                case "Int32":
                                    WriteLine("case \"{0}\": {0} = value.ToInt(); break;", column.Name);
                                    break;
                                case "Int64":
                                    WriteLine("case \"{0}\": {0} = value.ToLong(); break;", column.Name);
                                    break;
                                case "Double":
                                    WriteLine("case \"{0}\": {0} = value.ToDouble(); break;", column.Name);
                                    break;
                                case "Boolean":
                                    WriteLine("case \"{0}\": {0} = value.ToBoolean(); break;", column.Name);
                                    break;
                                case "DateTime":
                                    WriteLine("case \"{0}\": {0} = value.ToDateTime(); break;", column.Name);
                                    break;
                                default:
                                    WriteLine("case \"{0}\": {0} = Convert.To{1}(value); break;", column.Name, type);
                                    break;
                            }
                        }
                        else
                        {
                            try
                            {
                                // 特殊支持枚举
                                var type2 = type.GetTypeEx(false);
                                if (type2 != null && type2.IsEnum)
                                    WriteLine("case \"{0}\": {0} = ({1})value.ToInt(); break;", column.Name, type);
                                else
                                    WriteLine("case \"{0}\": {0} = ({1})value; break;", column.Name, type);
                            }
                            catch (Exception ex)
                            {
                                XTrace.WriteException(ex);
                                WriteLine("case \"{0}\": {0} = ({1})value; break;", column.Name, type);
                            }
                        }
                    }
                }
                WriteLine("default: throw new KeyNotFoundException($\"{name} not found\");");
                WriteLine("}");
            }
            WriteLine("}");

            WriteLine("}");
            WriteLine("#endregion");
        }

        /// <summary>生成拷贝函数</summary>
        /// <param name="model">模型类</param>
        protected virtual void BuildCopy(String model)
        {
            WriteLine("#region 拷贝");
            WriteLine("/// <summary>拷贝模型对象</summary>");
            WriteLine("/// <param name=\"model\">模型</param>");
            WriteLine("public void Copy({0} model)", model);
            WriteLine("{");
            foreach (var column in Table.Columns)
            {
                // 跳过排除项
                if (!ValidColumn(column, true)) continue;

                WriteLine("{0} = model.{0};", column.Name);
            }
            WriteLine("}");
            WriteLine("#endregion");
        }
        #endregion

        #region 写入缩进方法
        private String _Indent;

        /// <summary>设置缩进</summary>
        /// <param name="add"></param>
        protected virtual void SetIndent(Boolean add)
        {
            if (add)
                _Indent += "    ";
            else if (!_Indent.IsNullOrEmpty())
                _Indent = _Indent[0..^4];
        }

        /// <summary>写入</summary>
        /// <param name="value"></param>
        protected virtual void WriteLine(String value = null)
        {
            if (value.IsNullOrEmpty())
            {
                Writer.WriteLine();
                return;
            }

            if (value[0] == '}') SetIndent(false);

            var v = value;
            if (!_Indent.IsNullOrEmpty()) v = _Indent + v;

            Writer.WriteLine(v);

            if (value == "{") SetIndent(true);
        }

        /// <summary>写入</summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        protected virtual void WriteLine(String format, params Object[] args)
        {
            if (!_Indent.IsNullOrEmpty()) format = _Indent + format;

            Writer.WriteLine(format, args);
        }

        /// <summary>清空,重新生成</summary>
        public void Clear()
        {
            _Indent = null;

            if (Writer is StringWriter sw)
            {
                sw.GetStringBuilder().Clear();
            }
        }

        /// <summary>输出结果</summary>
        /// <returns></returns>
        public override String ToString() => Writer.ToString();
        #endregion

        #region 保存
        /// <summary>保存文件,返回文件路径</summary>
        public virtual String Save(String ext = null, Boolean overwrite = true, Boolean chineseFileName = true)
        {
            var p = Option.Output;

            if (ext.IsNullOrEmpty())
                ext = ".cs";
            else if (!ext.Contains("."))
                ext += ".cs";

            if (Option.Interface)
                p = p.CombinePath(ClassName + ext);
            else if (chineseFileName && !Table.DisplayName.IsNullOrEmpty())
                p = p.CombinePath(Table.DisplayName + ext);
            else
                p = p.CombinePath(ClassName + ext);

            p = p.GetBasePath();

            if (!File.Exists(p) || overwrite) File.WriteAllText(p.EnsureDirectory(true), ToString());

            return p;
        }
        #endregion

        #region 辅助
        /// <summary>验证字段是否可用于生成</summary>
        /// <param name="column"></param>
        /// <param name="validModel"></param>
        /// <returns></returns>
        protected virtual Boolean ValidColumn(IDataColumn column, Boolean validModel = false)
        {
            if (Option.Excludes.Contains(column.Name)) return false;
            if (Option.Excludes.Contains(column.ColumnName)) return false;
            if ((validModel || Option.Pure || Option.Interface) && column.Properties["Model"] == "False")
                return false;

            return true;
        }

        /// <summary>C#版本</summary>
        public Version CSharp { get; set; }

        /// <summary>nameof</summary>
        /// <param name="name"></param>
        /// <returns></returns>
        protected String NameOf(String name)
        {
            var v = CSharp;
            if (v == null || v.Major == 0 || v.Major > 5) return $"nameof({name})";

            return "\"" + name + "\"";
        }

        /// <summary>驼峰命名,首字母小写</summary>
        /// <param name="name"></param>
        /// <returns></returns>
        protected static String GetCamelCase(String name)
        {
            if (name.EqualIgnoreCase("id")) return "id";

            return Char.ToLower(name[0]) + name[1..];
        }

        /// <summary>是否调试</summary>
        public static Boolean Debug { get; set; }

        /// <summary>日志</summary>
        public ILog Log { get; set; } = Logger.Null;

        /// <summary>写日志</summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        public void WriteLog(String format, params Object[] args) => Log?.Info(format, args);
        #endregion
    }
}