luban/src/Luban.Job.Common/Source/Defs/CommonDefLoader.cs

448 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Luban.Common.Utils;
using Luban.Job.Common.RawDefs;
using Luban.Job.Common.Utils;
using Luban.Server.Common;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Luban.Job.Common.Defs
{
public abstract class CommonDefLoader
{
private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
protected IAgent Agent { get; }
public string RootDir { get; private set; }
public bool IsBeanDefaultCompatible { get; protected set; }
public bool IsBeanFieldMustDefineId { get; protected set; }
private readonly Dictionary<string, Action<XElement>> _rootDefineHandlers = new Dictionary<string, Action<XElement>>();
private readonly Dictionary<string, Action<string, XElement>> _moduleDefineHandlers = new();
protected readonly Stack<string> _namespaceStack = new Stack<string>();
protected string TopModule { get; private set; }
protected readonly List<PEnum> _enums = new List<PEnum>();
protected readonly List<Bean> _beans = new List<Bean>();
protected readonly HashSet<string> _externalSelectors = new();
protected readonly Dictionary<string, ExternalType> _externalTypes = new();
protected readonly Dictionary<string, string> _options = new();
protected CommonDefLoader(IAgent agent)
{
Agent = agent;
_rootDefineHandlers.Add("topmodule", SetTopModule);
_rootDefineHandlers.Add("option", AddOption);
_rootDefineHandlers.Add("externalselector", AddExternalSelector);
_moduleDefineHandlers.Add("module", AddModule);
_moduleDefineHandlers.Add("enum", AddEnum);
_moduleDefineHandlers.Add("bean", AddBean);
_moduleDefineHandlers.Add("externaltype", AddExternalType);
}
public string RootXml => _rootXml;
private string _rootXml;
public async Task LoadAsync(string rootXml)
{
_rootXml = rootXml;
RootDir = FileUtil.GetParent(rootXml);
XElement doc = await Agent.OpenXmlAsync(rootXml);
foreach (XElement e in doc.Elements())
{
var tagName = e.Name.LocalName;
if (tagName == "import")
{
await AddImportAsync(XmlUtil.GetRequiredAttribute(e, "name"));
continue;
}
if (_rootDefineHandlers.TryGetValue(tagName, out var handler))
{
handler(e);
}
else
{
throw new LoadDefException($"定义文件:{rootXml} 非法 tag:{tagName}");
}
}
}
protected void RegisterRootDefineHandler(string name, Action<XElement> handler)
{
_rootDefineHandlers.Add(name, handler);
}
protected void RegisterModuleDefineHandler(string name, Action<string, XElement> handler)
{
_moduleDefineHandlers.Add(name, handler);
}
protected string CurNamespace => _namespaceStack.Count > 0 ? _namespaceStack.Peek() : "";
protected void BuildCommonDefines(DefinesCommon defines)
{
defines.TopModule = TopModule;
defines.Enums = _enums;
defines.Beans = _beans;
defines.ExternalSelectors = _externalSelectors;
defines.ExternalTypes = _externalTypes;
defines.Options = _options;
}
#region root handler
private void SetTopModule(XElement e)
{
this.TopModule = XmlUtil.GetOptionalAttribute(e, "name");
}
private static readonly List<string> _optionRequireAttrs = new List<string> { "name", "value", };
private void AddOption(XElement e)
{
ValidAttrKeys(_rootXml, e, null, _optionRequireAttrs);
string name = XmlUtil.GetRequiredAttribute(e, "name");
if (!_options.TryAdd(name, XmlUtil.GetRequiredAttribute(e, "value")))
{
throw new LoadDefException($"option name:'{name}' duplicate");
}
}
private async Task AddImportAsync(string xmlFile)
{
var rootFileName = FileUtil.GetFileName(_rootXml);
var xmlFullPath = FileUtil.Combine(RootDir, xmlFile);
s_logger.Trace("import {file} {full_path}", xmlFile, xmlFullPath);
var fileOrDirContent = await Agent.GetFileOrDirectoryAsync(xmlFullPath, ".xml");
if (fileOrDirContent.IsFile)
{
s_logger.Trace("== file:{file}", xmlFullPath);
AddModule(xmlFullPath, XmlUtil.Open(xmlFullPath, await Agent.GetFromCacheOrReadAllBytesAsync(xmlFullPath, fileOrDirContent.Md5)));
}
else
{
// 如果是目录,则递归导入目录下的所有 .xml 定义文件
foreach (var subFile in fileOrDirContent.SubFiles)
{
var subFileName = subFile.FilePath;
s_logger.Trace("sub import xmlfile:{file} root file:{root}", subFileName, rootFileName);
// 有时候 root 定义文件会跟 module定义文件放在一个目录. 当以目录形式导入子module时不希望导入它
if (FileUtil.GetFileName(subFileName) == rootFileName)
{
s_logger.Trace("ignore import root file:{root}", subFileName);
continue;
}
string subFullPath = subFileName;
AddModule(subFullPath, XmlUtil.Open(subFullPath, await Agent.GetFromCacheOrReadAllBytesAsync(subFullPath, subFile.MD5)));
}
}
}
#endregion
#region module handler
private void AddModule(string defineFile, XElement me)
{
var name = XmlUtil.GetOptionalAttribute(me, "name")?.Trim();
//if (string.IsNullOrEmpty(name))
//{
// throw new LoadDefException($"xml:{CurImportFile} contains module which's name is empty");
//}
_namespaceStack.Push(_namespaceStack.Count > 0 ? TypeUtil.MakeFullName(_namespaceStack.Peek(), name) : name);
// 加载所有module定义,允许嵌套
foreach (XElement e in me.Elements())
{
var tagName = e.Name.LocalName;
if (_moduleDefineHandlers.TryGetValue(tagName, out var handler))
{
if (tagName != "module")
{
handler(defineFile, e);
}
else
{
handler(defineFile, e);
}
}
else
{
throw new LoadDefException($"定义文件:{defineFile} module:{CurNamespace} 不支持 tag:{tagName}");
}
}
_namespaceStack.Pop();
}
private static readonly List<string> _fieldRequireAttrs = new List<string> { "name", "type", };
private static readonly List<string> _fieldOptionalAttrs = new List<string> { "id", "comment", "tags" };
protected virtual Field CreateField(string defineFile, XElement e)
{
ValidAttrKeys(defineFile, e, _fieldOptionalAttrs, _fieldRequireAttrs);
var f = new Field()
{
Id = XmlUtil.GetOptionIntAttribute(e, "id"),
Name = XmlUtil.GetRequiredAttribute(e, "name"),
Type = CreateType(e, "type"),
Comment = XmlUtil.GetOptionalAttribute(e, "comment"),
Tags = XmlUtil.GetOptionalAttribute(e, "tags"),
};
return f;
}
protected void AddBean(string defineFile, XElement e)
{
AddBean(defineFile, e, "");
}
private static readonly List<string> _beanOptinsAttrs1 = new List<string> { "compatible", "value_type", "comment", "tags", "externaltype" };
private static readonly List<string> _beanRequireAttrs1 = new List<string> { "id", "name" };
private static readonly List<string> _beanOptinsAttrs2 = new List<string> { "id", "parent", "compatible", "value_type", "comment", "tags"};
private static readonly List<string> _beanRequireAttrs2 = new List<string> { "name" };
protected void TryGetUpdateParent(XElement e, ref string parent)
{
string selfDefParent = XmlUtil.GetOptionalAttribute(e, "parent");
if (!string.IsNullOrEmpty(selfDefParent))
{
if (!string.IsNullOrEmpty(parent))
{
throw new Exception($"嵌套在'{parent}'中定义的子bean:'{XmlUtil.GetRequiredAttribute(e, "name")}' 不能再定义parent:{selfDefParent} 属性");
}
parent = selfDefParent;
}
}
protected virtual void AddBean(string defineFile, XElement e, string parent)
{
if (IsBeanFieldMustDefineId)
{
ValidAttrKeys(defineFile, e, _beanOptinsAttrs1, _beanRequireAttrs1);
}
else
{
ValidAttrKeys(defineFile, e, _beanOptinsAttrs2, _beanRequireAttrs2);
}
TryGetUpdateParent(e, ref parent);
var b = new Bean()
{
Name = XmlUtil.GetRequiredAttribute(e, "name").Trim(),
Namespace = CurNamespace,
Parent = parent,
TypeId = XmlUtil.GetOptionIntAttribute(e, "id"),
IsSerializeCompatible = XmlUtil.GetOptionBoolAttribute(e, "compatible", IsBeanDefaultCompatible),
IsValueType = XmlUtil.GetOptionBoolAttribute(e, "value_type"),
Comment = XmlUtil.GetOptionalAttribute(e, "comment"),
Tags = XmlUtil.GetOptionalAttribute(e, "tags"),
};
var childBeans = new List<XElement>();
bool defineAnyChildBean = false;
foreach (XElement fe in e.Elements())
{
switch (fe.Name.LocalName)
{
case "var":
{
if (defineAnyChildBean)
{
throw new LoadDefException($"定义文件:{defineFile} 类型:{b.FullName} 的多态子bean必须在所有成员字段 <var> 之前定义");
}
b.Fields.Add(CreateField(defineFile, fe)); ;
break;
}
case "bean":
{
defineAnyChildBean = true;
childBeans.Add(fe);
break;
}
default:
{
throw new LoadDefException($"定义文件:{defineFile} 类型:{b.FullName} 不支持 tag:{fe.Name}");
}
}
}
s_logger.Trace("add bean:{@bean}", b);
_beans.Add(b);
var fullname = b.FullName;
foreach (var cb in childBeans)
{
AddBean(defineFile, cb, fullname);
}
}
protected static string CreateType(XElement e, string key)
{
return XmlUtil.GetRequiredAttribute(e, key);
}
protected void ValidAttrKeys(string defineFile, XElement e, List<string> optionKeys, List<string> requireKeys)
{
foreach (var k in e.Attributes())
{
var name = k.Name.LocalName;
if (!requireKeys.Contains(name) && (optionKeys != null && !optionKeys.Contains(name)))
{
throw new LoadDefException($"定义文件:{defineFile} module:{CurNamespace} 定义:{e} 包含未知属性 attr:{name}");
}
}
foreach (var k in requireKeys)
{
if (e.Attribute(k) == null)
{
throw new LoadDefException($"定义文件:{defineFile} module:{CurNamespace} 定义:{e} 缺失属性 attr:{k}");
}
}
}
private static readonly List<string> _enumOptionalAttrs = new List<string> { "flags", "comment", "tags", "unique" };
private static readonly List<string> _enumRequiredAttrs = new List<string> { "name" };
private static readonly List<string> _enumItemOptionalAttrs = new List<string> { "value", "alias", "comment", "tags" };
private static readonly List<string> _enumItemRequiredAttrs = new List<string> { "name" };
protected void AddEnum(string defineFile, XElement e)
{
ValidAttrKeys(defineFile, e, _enumOptionalAttrs, _enumRequiredAttrs);
var en = new PEnum()
{
Name = XmlUtil.GetRequiredAttribute(e, "name").Trim(),
Namespace = CurNamespace,
Comment = XmlUtil.GetOptionalAttribute(e, "comment"),
IsFlags = XmlUtil.GetOptionBoolAttribute(e, "flags"),
Tags = XmlUtil.GetOptionalAttribute(e, "tags"),
IsUniqueItemId = XmlUtil.GetOptionBoolAttribute(e, "unique", true),
};
foreach (XElement item in e.Elements())
{
ValidAttrKeys(defineFile, item, _enumItemOptionalAttrs, _enumItemRequiredAttrs);
en.Items.Add(new EnumItem()
{
Name = XmlUtil.GetRequiredAttribute(item, "name"),
Alias = XmlUtil.GetOptionalAttribute(item, "alias"),
Value = XmlUtil.GetOptionalAttribute(item, "value"),
Comment = XmlUtil.GetOptionalAttribute(item, "comment"),
Tags = XmlUtil.GetOptionalAttribute(item, "tags"),
});
}
s_logger.Trace("add enum:{@enum}", en);
_enums.Add(en);
}
private static readonly List<string> _selectorRequiredAttrs = new List<string> { "name" };
private void AddExternalSelector(XElement e)
{
ValidAttrKeys(_rootXml, e, null, _selectorRequiredAttrs);
string name = XmlUtil.GetRequiredAttribute(e, "name");
if (!_externalSelectors.Add(name))
{
throw new LoadDefException($"定义文件:{_rootXml} externalselector name:{name} 重复");
}
s_logger.Trace("add selector:{}", name);
}
private static readonly List<string> _externalRequiredAttrs = new List<string> { "name", "origin_type_name" };
private void AddExternalType(string defineFile, XElement e)
{
ValidAttrKeys(_rootXml, e, null, _externalRequiredAttrs);
string name = XmlUtil.GetRequiredAttribute(e, "name");
if (_externalTypes.ContainsKey(name))
{
throw new LoadDefException($"定义文件:{_rootXml} externaltype:{name} 重复");
}
var et = new ExternalType()
{
Name = name,
OriginTypeName = XmlUtil.GetRequiredAttribute(e, "origin_type_name"),
};
var mappers = new Dictionary<string, ExternalTypeMapper>();
foreach (XElement mapperEle in e.Elements())
{
var tagName = mapperEle.Name.LocalName;
if (tagName == "mapper")
{
var mapper = CreateMapper(defineFile, name, mapperEle);
string uniqKey = $"{mapper.Lan}##{mapper.Selector}";
if (mappers.ContainsKey(uniqKey))
{
throw new LoadDefException($"定义文件:{_rootXml} externaltype name:{name} mapper(lan='{mapper.Lan}',selector='{mapper.Selector}') 重复");
}
mappers.Add(uniqKey, mapper);
et.Mappers.Add(mapper);
s_logger.Trace("add mapper. externaltype:{} mapper:{@}", name, mapper);
}
else
{
throw new LoadDefException($"定义文件:{defineFile} externaltype:{name} 非法 tag:'{tagName}'");
}
}
_externalTypes.Add(name, et);
}
private static readonly List<string> _mapperOptionalAttrs = new List<string> { };
private static readonly List<string> _mapperRequiredAttrs = new List<string> { "lan", "selector" };
private ExternalTypeMapper CreateMapper(string defineFile, string externalType, XElement e)
{
ValidAttrKeys(_rootXml, e, _mapperOptionalAttrs, _mapperRequiredAttrs);
var m = new ExternalTypeMapper()
{
Lan = DefUtil.ParseLanguage(XmlUtil.GetRequiredAttribute(e, "lan")),
Selector = XmlUtil.GetRequiredAttribute(e, "selector"),
};
foreach (XElement attrEle in e.Elements())
{
var tagName = attrEle.Name.LocalName;
switch (tagName)
{
case "target_type_name":
{
m.TargetTypeName = attrEle.Value;
break;
}
case "create_external_object_function":
{
m.CreateExternalObjectFunction = attrEle.Value;
break;
}
default: throw new LoadDefException($"定义文件:{defineFile} externaltype:{externalType} 非法 tag:{tagName}");
}
}
if (string.IsNullOrWhiteSpace(m.TargetTypeName))
{
throw new LoadDefException($"定义文件:{defineFile} externaltype:{externalType} lan:{m.Lan} selector:{m.Selector} 没有定义 'target_type_name'");
}
return m;
}
#endregion
}
}