JD2022-TU1/main/tools/framework/JD.TeaBoxAddinSystem/Addins/Management/AddinXmlSerializerReaderSerializer.cs

817 lines
30 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using JD.Kernel.StaticHelpers;
using JD.Kernel.Structs;
using JD.LogService;
using JD.Serializers.Xml;
using JD.TeaBoxAddinSystem.Addins.Description;
using JD.TeaBoxAddinSystem.Addins.Description.Conditions;
using JD.TeaBoxAddinSystem.Addins.Description.FileReferences;
namespace JD.TeaBoxAddinSystem.Addins.Management
{
/// <summary>
/// An object that serializes an <see cref="Addin"/> from an <see cref="XmlSerializerReader"/>
/// </summary>
internal class AddinXmlSerializerReaderSerializer : AddinXmlSerializer
{
#region Variables
private readonly XmlSerializerReader m_reader;
#endregion
#region Constructor
internal AddinXmlSerializerReaderSerializer(XmlSerializerReader reader, ILogger logger)
: base(logger)
{
Throw.IfNull(reader, "reader");
m_reader = reader;
}
#endregion
#region Properties
/// <summary>
/// Overriden.
/// </summary>
protected override FileLocation CurrentFileLocation
{
get { return new FileLocation(m_reader.FileName, m_reader.LineNumber, m_reader.ColumnNumber); }
}
#endregion
#region Methods
/// <summary>
/// Overriden.
/// </summary>
/// <returns></returns>
protected override Addin Serialize()
{
if (!string.IsNullOrEmpty(m_reader.FileName))
{
LogMessage("Reading addin from " + m_reader.FileName, MessageImportance.Low);
}
FileLocation fileLocation = CurrentFileLocation;
Addin addin = new Addin(m_reader.Serialize(IdAttribute));
addin.FileLocation = fileLocation;
addin.MainModule.FileLocation = null; // Override the debug file location.
// Read manifest
if (m_reader.AttributeExists(NameAttribute))
{
addin.Manifest.Name = m_reader.Serialize(NameAttribute);
}
else
{
addin.Manifest.Name = addin.Manifest.Id;
}
Version version = ParseVersion(m_reader.Serialize(VersionAttribute));
if (version != null)
{
addin.Manifest.Version = version;
}
addin.Manifest.Author = m_reader.Serialize(AuthorAttribute);
addin.Manifest.Url = m_reader.Serialize(UrlAttribute);
addin.Manifest.Description = m_reader.Serialize(DescriptionAttribute);
addin.Manifest.Category = m_reader.Serialize(CategoryAttribute);
addin.Manifest.X86Only = m_reader.Serialize<bool>(X86OnlyAttribute);
ReadModule(addin.MainModule);
return addin;
}
private void ReadModule(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
System.Diagnostics.Debug.Assert(module.Addin != null);
bool isMainModule = module.IsMainModule;
Addin addin = module.Addin;
foreach (string element in m_reader)
{
if (isMainModule && string.Equals(element, IncludeNode, StringComparison.OrdinalIgnoreCase))
{
Include(addin);
}
else if (isMainModule && string.Equals(element, ExtensionNodeSetNode, StringComparison.OrdinalIgnoreCase))
{
ExtensionTypeSet typeSet = ReadExtensionTypeSet();
if (typeSet != null)
{
if (addin.ExtensionTypeSets.Contains(typeSet.Name))
{
LogDuplicateElement("extension node set", typeSet.Name, addin.ExtensionTypeSets[typeSet.Name].FileLocation);
}
else
{
addin.ExtensionTypeSets.Add(typeSet);
}
}
}
else if (isMainModule && string.Equals(element, ExtensionPointNode, StringComparison.OrdinalIgnoreCase))
{
ExtensionPoint point = ReadExtensionPoint();
if (point != null)
{
if (addin.ExtensionPoints.Contains(point.Path))
{
LogDuplicateElement("extension point", point.Path, addin.ExtensionPoints[point.Path].FileLocation);
}
else
{
addin.ExtensionPoints.Add(point);
}
}
}
else if (isMainModule && string.Equals(element, ConditionNodeNode, StringComparison.OrdinalIgnoreCase))
{
ConditionType conditionType = ReadConditionType();
if (conditionType != null)
{
if (addin.ConditionTypes.Contains(conditionType.Name))
{
LogDuplicateElement("condition node", conditionType.Name, addin.ConditionTypes[conditionType.Name].FileLocation);
}
else
{
addin.ConditionTypes.Add(conditionType);
}
}
}
else if (string.Equals(element, FilesNode, StringComparison.OrdinalIgnoreCase))
{
ReadFiles(module);
}
else if (string.Equals(element, DependenciesNode, StringComparison.OrdinalIgnoreCase))
{
ReadDependencies(module);
}
else if (string.Equals(element, ExtensionNode, StringComparison.OrdinalIgnoreCase))
{
ReadExtension(module);
}
else if (string.Equals(element, ModuleNode, StringComparison.OrdinalIgnoreCase))
{
if (isMainModule)
{
Module newModule = new Module();
newModule.FileLocation = CurrentFileLocation;
addin.Modules.Add(newModule);
ReadModule(newModule);
}
else
{
LogError("Nested modules are not supported");
}
}
else
{
LogUnexpectedXmlElement(element);
}
}
}
private ExtensionPoint ReadExtensionPoint()
{
string path = m_reader.Serialize(PathAttribute);
if (string.IsNullOrEmpty(path))
{
LogInvalidAttributeElement(PathAttribute, ExtensionPointNode);
return null;
}
ExtensionPoint point = new ExtensionPoint(path);
point.FileLocation = CurrentFileLocation;
point.Name = m_reader.Serialize(NameAttribute);
point.DeprecationMessage = m_reader.Serialize(DeprecatedAttribute);
// Read children
ReadExtensionTypeSetBase(point.ExtensionTypeSet, ExtensionPointNode);
point.Description = point.ExtensionTypeSet.Description;
return point;
}
private ExtensionTypeSet ReadExtensionTypeSet()
{
string name = m_reader.Serialize(NameAttribute);
if (string.IsNullOrEmpty(name))
{
LogInvalidAttributeElement(NameAttribute, ExtensionNodeSetNode);
return null;
}
ExtensionTypeSet extensionTypeSet = new ExtensionTypeSet(name);
extensionTypeSet.FileLocation = CurrentFileLocation;
// Read children
ReadExtensionTypeSetBase(extensionTypeSet, ExtensionNodeSetNode);
return extensionTypeSet;
}
private void ReadExtensionTypeSetBase(ExtensionTypeSet extensionTypeSet, string nodeName)
{
System.Diagnostics.Debug.Assert(extensionTypeSet != null);
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(nodeName));
FileLocation? descriptionLocation = null;
if (m_reader.AttributeExists(DescriptionAttribute))
{
extensionTypeSet.Description = m_reader.Serialize(DescriptionAttribute);
descriptionLocation = CurrentFileLocation;
}
foreach (string element in m_reader)
{
if (string.Equals(element, ExtensionNodeNode, StringComparison.OrdinalIgnoreCase))
{
ExtensionType extensionType = ReadExtensionType();
if (extensionType != null)
{
if (extensionTypeSet.ExtensionTypes.Contains(extensionType.Name))
{
LogDuplicateElement("extension node", extensionType.Name, extensionTypeSet.ExtensionTypes[extensionType.Name].FileLocation);
}
else
{
ReadExtensionTypeSetBase(extensionType.ExtensionTypeSet, ExtensionNodeNode);
extensionType.Description = extensionType.ExtensionTypeSet.Description;
extensionTypeSet.ExtensionTypes.Add(extensionType);
}
}
}
else if (string.Equals(element, ExtensionNodeSetNode, StringComparison.OrdinalIgnoreCase))
{
CheckIsEmpty(ExtensionNodeSetNode);
string nameReference = m_reader.Serialize(NameAttribute);
if (string.IsNullOrEmpty(nameReference))
{
LogInvalidAttributeElement(NameAttribute, ExtensionNodeSetNode);
}
else
{
extensionTypeSet.ExtensionTypeSets.Add(nameReference);
}
}
else if (string.Equals(element, DescriptionNode, StringComparison.OrdinalIgnoreCase))
{
if (descriptionLocation != null)
{
LogDescriptionAlreadyDefined(nodeName, descriptionLocation);
}
else
{
descriptionLocation = CurrentFileLocation;
extensionTypeSet.Description = m_reader.ReadInnerData();
}
}
else
{
LogUnexpectedXmlElement(element);
}
}
}
private ExtensionType ReadExtensionType()
{
string name = m_reader.Serialize(NameAttribute);
if (string.IsNullOrEmpty(name))
{
LogInvalidAttributeElement(NameAttribute, ExtensionNodeNode);
return null;
}
// Validate name
if (name.StartsWith(ConditionPrefix, StringComparison.OrdinalIgnoreCase))
{
LogError($"Extension nodes cannot have a name that starts with \"{ConditionPrefix}\" because it is reserved for condition nodes");
return null;
}
ExtensionType extensionType = new ExtensionType(name);
extensionType.FileLocation = CurrentFileLocation;
extensionType.TypeName = m_reader.Serialize(TypeAttribute);
extensionType.ObjectTypeName = m_reader.Serialize(ObjectTypeAttribute);
return extensionType;
}
private ConditionType ReadConditionType()
{
string name = m_reader.Serialize(NameAttribute);
if (string.IsNullOrEmpty(name))
{
LogInvalidAttributeElement(NameAttribute, ConditionNodeNode);
return null;
}
string typename = m_reader.Serialize(TypeAttribute);
if (string.IsNullOrEmpty(typename))
{
LogInvalidAttributeElement(TypeAttribute, ConditionNodeNode);
return null;
}
ConditionType conditionType = new ConditionType(name, typename);
conditionType.FileLocation = CurrentFileLocation;
FileLocation? descriptionLocation = null;
if (m_reader.AttributeExists(DescriptionAttribute))
{
conditionType.Description = m_reader.Serialize(DescriptionAttribute);
descriptionLocation = CurrentFileLocation;
}
if (m_reader.StartElement(DescriptionNode, false, false) != null)
{
if (descriptionLocation != null)
{
LogDescriptionAlreadyDefined(ConditionNodeNode, descriptionLocation);
}
else
{
conditionType.Description = m_reader.ReadInnerData();
descriptionLocation = CurrentFileLocation;
}
}
return conditionType;
}
private void ReadFiles(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
foreach (string element in m_reader)
{
if (string.Equals(element, ImportNode, StringComparison.OrdinalIgnoreCase))
{
ReadImportNode(module);
}
else
{
LogUnexpectedXmlElement(element);
}
}
}
private void ReadImportNode(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
if (m_reader.AttributeCount > 1)
{
// Oops, more than one specified
LogError($"Cannot specify more than one attribute in a \"{ImportNode}\" element");
}
if (m_reader.AttributeExists(AssemblyAttribute))
{
string assemblyFile = m_reader.Serialize(AssemblyAttribute);
if (string.IsNullOrEmpty(assemblyFile))
{
LogInvalidAttributeElement(AssemblyAttribute, ImportNode);
}
else
{
AssemblyFileReference assembly = new AssemblyFileReference(assemblyFile, System.IO.Path.GetDirectoryName(m_reader.FileName));
assembly.FileLocation = CurrentFileLocation;
ReadImportAssemblyNode(assembly);
module.Assemblies.Add(assembly);
}
}
else if (m_reader.AttributeExists(FileAttribute))
{
CheckIsEmpty(ImportNode);
string file = m_reader.Serialize(FileAttribute);
if (string.IsNullOrEmpty(file))
{
LogInvalidAttributeElement(FileAttribute, ImportNode);
}
else
{
FileReference fileReference = new FileReference(file);
fileReference.FileLocation = CurrentFileLocation;
module.OtherFiles.Add(fileReference);
}
}
else if (m_reader.AttributeExists(ResourceAttribute))
{
CheckIsEmpty(ImportNode);
string resource = m_reader.Serialize(ResourceAttribute);
if (string.IsNullOrEmpty(resource))
{
LogInvalidAttributeElement(ResourceAttribute, ImportNode);
}
else
{
ResourceFileReference resourceReference = new ResourceFileReference(resource);
resourceReference.FileLocation = CurrentFileLocation;
module.ResourceFiles.Add(resourceReference);
}
}
else
{
// No attribute!
LogInvalidAttributeElement($"{AssemblyAttribute}\", \"{ResourceAttribute}\" or \"{FileAttribute}", ImportNode);
}
}
private void ReadImportAssemblyNode(AssemblyFileReference reference)
{
System.Diagnostics.Debug.Assert(reference != null);
foreach (string element in m_reader)
{
if (string.Equals(element, ImportNode, StringComparison.OrdinalIgnoreCase))
{
CheckIsEmpty(ImportNode);
string resource = m_reader.Serialize(ResourceAttribute);
if (m_reader.AttributeCount != 1 || string.IsNullOrEmpty(resource))
{
LogError($"Only {ResourceAttribute} imports are allowed in {AssemblyAttribute} imports");
}
else
{
AssemblyResourceFileReference resourceReference = new AssemblyResourceFileReference(resource);
resourceReference.FileLocation = CurrentFileLocation;
reference.ResourceFiles.Add(resourceReference);
}
}
else
{
LogUnexpectedXmlElement(element);
}
}
}
private void ReadDependencies(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
foreach (string element in m_reader)
{
if (string.Equals(element, AddinNode, StringComparison.OrdinalIgnoreCase))
{
AddinReference reference = ReadAddinNode();
if (reference != null)
{
module.Dependencies.Add(reference);
}
}
else
{
LogUnexpectedXmlElement(element);
}
}
}
private AddinReference ReadAddinNode()
{
CheckIsEmpty(AddinNode);
string id = m_reader.Serialize(IdAttribute);
if (string.IsNullOrEmpty(id))
{
LogInvalidAttributeElement(IdAttribute, AddinNode);
return null;
}
string versionString = m_reader.Serialize(VersionAttribute);
if (string.IsNullOrEmpty(versionString))
{
return new AddinReference(id);
}
// Try to split the version into a version range
int indexOfHyphen = versionString.IndexOf('-');
if (indexOfHyphen != -1)
{
string minVersionString = versionString.Substring(0, indexOfHyphen);
Version minVersion = string.IsNullOrEmpty(minVersionString) ? AddinReference.ZeroVersion : ParseVersion(minVersionString);
string maxVersionString = versionString.Substring(indexOfHyphen + 1);
Version maxVersion = string.IsNullOrEmpty(maxVersionString) ? AddinReference.InfiniteVersion : ParseVersion(maxVersionString);
if (minVersion == null || maxVersion == null)
{
return null;
}
if (AddinReference.Compare(minVersion, maxVersion) > 0)
{
LogError("Invalid version range: min version is larger than max version");
return null;
}
return new AddinReference(id, minVersion, maxVersion);
}
else
{
Version version = ParseVersion(versionString);
return version == null ? null : new AddinReference(id, version);
}
}
private void ReadExtension(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
string path = m_reader.Serialize(PathAttribute);
if (string.IsNullOrEmpty(path))
{
LogInvalidAttributeElement(PathAttribute, ExtensionNode);
return;
}
Extension extension = new Extension(path);
extension.FileLocation = CurrentFileLocation;
module.Extensions.Add(extension);
ReadExtensionNodes(extension.ExtensionNodes, new Stack<ConditionInfo>());
}
private void ReadExtensionNodes(ICollection<ExtensionNodeDescription> nodes, Stack<ConditionInfo> conditionStack)
{
System.Diagnostics.Debug.Assert(nodes != null);
System.Diagnostics.Debug.Assert(!nodes.IsReadOnly);
foreach (string element in m_reader)
{
if (string.Equals(element, ConditionPrefix, StringComparison.OrdinalIgnoreCase) || element.StartsWith(ConditionPrefix + ".", StringComparison.OrdinalIgnoreCase))
{
string target = m_reader.Serialize(TargetAttribute);
Condition condition = null;
if (string.Equals(element, ConditionPrefix, StringComparison.OrdinalIgnoreCase))
{
// Complex condition
// First element must be a conditional operator
using (IXmlElement conditionElement = m_reader.StartElement())
{
if (conditionElement != null)
{
condition = ReadConditionalOperator(conditionElement.Name, false);
}
if (condition == null)
{
LogError("First element of a complex condition must be a condition operator (And, Or, Not, Xor)");
continue;
}
}
}
else
{
System.Diagnostics.Debug.Assert(element.StartsWith(ConditionPrefix + ".", StringComparison.OrdinalIgnoreCase));
// Simple condition
condition = ReadConditionalOperator(element, true);
}
System.Diagnostics.Debug.Assert(condition != null);
conditionStack.Push(new ConditionInfo(condition, target));
ReadExtensionNodes(nodes, conditionStack);
conditionStack.Pop();
}
else
{
ExtensionNodeDescription extensionNodeDescription = new ExtensionNodeDescription(element);
extensionNodeDescription.FileLocation = CurrentFileLocation;
ReadNodeDescription(extensionNodeDescription);
// Add current conditions
foreach (ConditionInfo conditionInfo in conditionStack)
{
extensionNodeDescription.Conditions.Add(conditionInfo);
}
nodes.Add(extensionNodeDescription);
ReadExtensionNodes(extensionNodeDescription.ChildNodes, new Stack<ConditionInfo>());
}
}
}
private void ReadNodeDescription(NodeDescription nodeDescription)
{
System.Diagnostics.Debug.Assert(nodeDescription != null);
foreach (KeyValuePair<string, string> attribute in m_reader.GetAttributes())
{
nodeDescription.Attributes[attribute.Key] = attribute.Value;
}
}
private void Include(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
string filename = m_reader.Serialize(FileAttribute);
if (string.IsNullOrEmpty(filename))
{
LogInvalidAttributeElement(FileAttribute, IncludeNode);
return;
}
if (!FileUtility.IsAbsolutePath(filename))
{
// Transform to absolute
filename = FileUtility.FormatPath(Path.GetDirectoryName(m_reader.FileName) + Path.DirectorySeparatorChar + filename);
}
if (!File.Exists(filename))
{
LogError("Cannot find filename " + filename);
return;
}
using (XmlSerializerReader reader = new XmlSerializerReader(filename))
{
LogMessage("Including partial addin from " + filename, MessageImportance.Low);
addin.AddPartialAddinFilename(filename);
new AddinXmlSerializerReaderSerializer(reader, m_logger).ReadPartialAddin(addin);
}
}
/// <summary>
/// Entry point for partial addin reader.
/// </summary>
/// <param name="addin"></param>
private void ReadPartialAddin(Addin addin)
{
using (IXmlElement element = m_reader.StartElement(AddinNode, false, false))
{
if (element == null)
{
return;
}
if (m_reader.AttributeCount != 1 || !m_reader.AttributeExists(IdAttribute))
{
LogError($"Partial addin declaration must include only the {IdAttribute} attribute.");
}
string id = m_reader.Serialize(IdAttribute);
if (addin.FullId != id)
{
LogError($"Partial addin id {id} does not correspond to addin {addin.FullId}");
return;
}
ReadModule(addin.MainModule);
}
}
#region Complex conditions
private Condition ReadConditionalOperator(string name, bool allowBasicCondition)
{
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(name));
FileLocation fileLocation = CurrentFileLocation;
Condition condition = null;
if (allowBasicCondition && name.StartsWith(ConditionPrefix + ".", StringComparison.OrdinalIgnoreCase))
{
// Simple condition
ConditionNodeDescription conditionNodeDescription = new ConditionNodeDescription(name.Substring(ConditionPrefix.Length + 1));
conditionNodeDescription.FileLocation = CurrentFileLocation;
ReadNodeDescription(conditionNodeDescription);
conditionNodeDescription.Attributes.Remove(TargetAttribute);
condition = new BasicCondition(conditionNodeDescription);
}
else if (string.Equals(name, AndNode, StringComparison.OrdinalIgnoreCase))
{
condition = Condition.And(ReadInnerConditionList().ToArray());
}
else if (string.Equals(name, OrNode, StringComparison.OrdinalIgnoreCase))
{
condition = Condition.Or(ReadInnerConditionList().ToArray());
}
else if (string.Equals(name, XorNode, StringComparison.OrdinalIgnoreCase))
{
condition = Condition.Xor(ReadInnerConditionList().ToArray());
}
else if (string.Equals(name, NotNode, StringComparison.OrdinalIgnoreCase))
{
Condition notCondition = null;
bool noElements = true;
foreach (string element in m_reader)
{
noElements = false;
if (notCondition != null)
{
LogError($"{NotNode} conditional operator must contain one and only one child element");
break;
}
notCondition = ReadConditionalOperator(element, true);
if (notCondition == null)
{
LogUnexpectedXmlElement(element);
}
}
if (notCondition == null)
{
if (noElements)
{
LogError($"{NotNode} conditional operator must contain one and only one child element");
}
condition = Condition.False;
}
else
{
condition = Condition.Not(notCondition);
}
}
else if (string.Equals(name, FalseNode, StringComparison.OrdinalIgnoreCase))
{
condition = Condition.False;
}
else if (string.Equals(name, TrueNode, StringComparison.OrdinalIgnoreCase))
{
condition = Condition.True;
}
if (condition != null)
{
condition.FileLocation = fileLocation;
}
return condition;
}
private List<Condition> ReadInnerConditionList()
{
List<Condition> conditionList = new List<Condition>();
foreach (string element in m_reader)
{
Condition readCondition = ReadConditionalOperator(element, true);
if (readCondition == null)
{
LogUnexpectedXmlElement(element);
continue;
}
conditionList.Add(readCondition);
}
return conditionList;
}
#endregion
private void CheckIsEmpty(string currentNodeName)
{
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(currentNodeName));
if (!m_reader.IsEmptyElement)
{
LogError(string.Format(ElementMustBeEmpty, currentNodeName));
}
}
#endregion
}
}