// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Data { using System; using System.Diagnostics; using SimpleJson; /// /// Intermediate symbol. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class IntermediateSymbol { private object tags; /// /// Creates an intermediate symbol. /// /// Symbol definition. public IntermediateSymbol(IntermediateSymbolDefinition definition) : this(definition, null, null) { } /// /// Creates an intermediate symbol with source line number and identifier. /// /// Symbol definition. /// Source line number. /// Symbol identifier. public IntermediateSymbol(IntermediateSymbolDefinition definition, SourceLineNumber sourceLineNumber, Identifier id = null) { this.Definition = definition; this.Fields = new IntermediateField[definition.FieldDefinitions.Length]; this.SourceLineNumbers = sourceLineNumber; this.Id = id; } /// /// Gets the symbol's definition. /// public IntermediateSymbolDefinition Definition { get; } /// /// Gets the symbol's fields. /// public IntermediateField[] Fields { get; } /// /// Gets the optional source line number of the symbol. /// public SourceLineNumber SourceLineNumbers { get; internal set; } /// /// Gets the optional identifier for the symbol. /// public Identifier Id { get; internal set; } /// /// Direct access by index to the symbol's fields. /// /// Index of the field to access. /// Symbol's field. public IntermediateField this[int index] => this.Fields[index]; private string DebuggerDisplay => $"{this.Definition?.Name} {this.Id?.Id}"; /// /// Add a custom tag to the symbol. /// /// String tag to add to the symbol. /// True if the tag was added; otherwise false if th tag was already present. public bool AddTag(string add) { if (this.tags == null) { this.tags = add; } else if (this.tags is string tag) { if (tag == add) { return false; } this.tags = new[] { tag, add }; } else { var tagsArray = (string[])this.tags; var array = new string[tagsArray.Length + 1]; for (var i = 0; i < tagsArray.Length; ++i) { if (tagsArray[i] == add) { return false; } array[i] = tagsArray[i]; } array[tagsArray.Length] = add; this.tags = array; } return true; } /// /// Tests whether a symbol has a tag. /// /// String tag to find. /// True if the symbol has the tag; otherwise false. public bool HasTag(string has) { if (this.tags == null) { return false; } else if (this.tags is string tag) { return tag == has; } else { foreach (var element in (string[])this.tags) { if (element == has) { return true; } } } return false; } /// /// Removes a tag from the symbol. /// /// String tag to remove. /// True if the tag was removed; otherwise false if the tag was not present. public bool RemoveTag(string remove) { if (this.tags is string tag) { if (tag == remove) { this.tags = null; return true; } } else if (this.tags is string[] tagsArray) { if (tagsArray.Length == 2) { if (tagsArray[0] == remove) { this.tags = tagsArray[1]; return true; } else if (tagsArray[1] == remove) { this.tags = tagsArray[0]; return true; } } else { var array = new string[tagsArray.Length - 1]; var arrayIndex = 0; var found = false; for (var i = 0; i < tagsArray.Length; ++i) { if (tagsArray[i] == remove) { found = true; continue; } else if (arrayIndex == array.Length) { break; } array[arrayIndex++] = tagsArray[i]; } if (found) { this.tags = array; return true; } } } return false; } internal static IntermediateSymbol Deserialize(ISymbolDefinitionCreator creator, Uri baseUri, JsonObject jsonObject) { var definitionName = jsonObject.GetValueOrDefault("type"); var idJson = jsonObject.GetValueOrDefault("id"); var sourceLineNumbersJson = jsonObject.GetValueOrDefault("ln"); var fieldsJson = jsonObject.GetValueOrDefault("fields"); var tagsJson = jsonObject.GetValueOrDefault("tags"); var id = (idJson == null) ? null : Identifier.Deserialize(idJson); var sourceLineNumbers = (sourceLineNumbersJson == null) ? null : SourceLineNumber.Deserialize(sourceLineNumbersJson); if (!creator.TryGetSymbolDefinitionByName(definitionName, out var definition)) { throw new WixException(ErrorMessages.UnknownSymbolType(definitionName)); } var symbol = definition.CreateSymbol(sourceLineNumbers, id); for (var i = 0; i < fieldsJson.Count && i < symbol.Fields.Length; ++i) { if (fieldsJson[i] is JsonObject fieldJson) { symbol.Fields[i] = IntermediateField.Deserialize(symbol.Definition.FieldDefinitions[i], baseUri, fieldJson); } } if (tagsJson == null || tagsJson.Count == 0) { } else if (tagsJson.Count == 1) { symbol.tags = (string)tagsJson[0]; } else { var tags = new string[tagsJson.Count]; for (var i = 0; i < tagsJson.Count; ++i) { tags[i] = (string)tagsJson[i]; } symbol.tags = tags; } return symbol; } internal JsonObject Serialize() { var jsonObject = new JsonObject { { "type", this.Definition.Name } }; var idJson = this.Id?.Serialize(); if (idJson != null) { jsonObject.Add("id", idJson); } var lnJson = this.SourceLineNumbers?.Serialize(); if (lnJson != null) { jsonObject.Add("ln", lnJson); } var fieldsJson = new JsonArray(this.Fields.Length); foreach (var field in this.Fields) { var fieldJson = field?.Serialize(); fieldsJson.Add(fieldJson); } jsonObject.Add("fields", fieldsJson); if (this.tags is string || this.tags is string[]) { JsonArray tagsJson; if (this.tags is string tag) { tagsJson = new JsonArray(1) { tag }; } else { var array = (string[])this.tags; tagsJson = new JsonArray(array.Length); tagsJson.AddRange(array); } jsonObject.Add("tags", tagsJson); } return jsonObject; } } }