// 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;
}
}
}