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 { /// /// An object that serializes an from an /// 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 /// /// Overriden. /// protected override FileLocation CurrentFileLocation { get { return new FileLocation(m_reader.FileName, m_reader.LineNumber, m_reader.ColumnNumber); } } #endregion #region Methods /// /// Overriden. /// /// 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(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()); } private void ReadExtensionNodes(ICollection nodes, Stack 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()); } } } private void ReadNodeDescription(NodeDescription nodeDescription) { System.Diagnostics.Debug.Assert(nodeDescription != null); foreach (KeyValuePair 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); } } /// /// Entry point for partial addin reader. /// /// 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 ReadInnerConditionList() { List conditionList = new List(); 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 } }