// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using SimpleJson;
///
/// Container class for an intermediate object.
///
public sealed class Intermediate
{
private static readonly Version CurrentVersion = new Version("4.0.0.0");
private const string WixOutputStreamName = "wix-ir.json";
private readonly Dictionary localizationsByCulture;
///
/// Instantiate a new Intermediate.
///
public Intermediate()
{
this.Id = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_');
this.localizationsByCulture = new Dictionary(StringComparer.OrdinalIgnoreCase);
this.Sections = new List();
}
public Intermediate(string id, IEnumerable sections, IDictionary localizationsByCulture) : this(id, level: null, sections, localizationsByCulture)
{
}
public Intermediate(string id, string level, IEnumerable sections, IDictionary localizationsByCulture)
{
this.Id = id;
this.Level = level;
this.localizationsByCulture = (localizationsByCulture != null) ? new Dictionary(localizationsByCulture, StringComparer.OrdinalIgnoreCase) : new Dictionary(StringComparer.OrdinalIgnoreCase);
this.Sections = (sections != null) ? new List(sections) : new List();
}
///
/// Get the id for the intermediate.
///
public string Id { get; }
///
/// Get the id for the intermediate.
///
public string Level { get; private set; }
///
/// Get the localizations contained in this intermediate.
///
public IEnumerable Localizations => this.localizationsByCulture.Values;
///
/// Get the sections contained in this intermediate.
///
public IList Sections { get; }
///
/// Loads an intermediate from a path on disk.
///
/// Path to intermediate file saved on disk.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(string path, bool suppressVersionCheck = false)
{
var creator = new SimpleTupleDefinitionCreator();
return Intermediate.Load(path, creator, suppressVersionCheck);
}
///
/// Loads an intermediate from a stream.
///
/// Assembly with intermediate embedded in resource stream.
/// Name of resource stream.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(Assembly assembly, string resourceName, bool suppressVersionCheck = false)
{
var creator = new SimpleTupleDefinitionCreator();
return Intermediate.Load(assembly, resourceName, creator, suppressVersionCheck);
}
///
/// Loads an intermediate from a stream.
///
/// Assembly with intermediate embedded in resource stream.
/// Name of resource stream.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(Assembly assembly, string resourceName, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
using (var wixout = WixOutput.Read(assembly, resourceName))
{
return Intermediate.LoadIntermediate(wixout, creator, suppressVersionCheck);
}
}
///
/// Loads an intermediate from a path on disk.
///
/// Path to intermediate file saved on disk.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(string path, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
using (var wixout = WixOutput.Read(path))
{
return Intermediate.LoadIntermediate(wixout, creator, suppressVersionCheck);
}
}
///
/// Loads an intermediate from a WixOutput object.
///
/// WixOutput object.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(WixOutput wixOutput, bool suppressVersionCheck = false)
{
var creator = new SimpleTupleDefinitionCreator();
return Intermediate.LoadIntermediate(wixOutput, creator, suppressVersionCheck);
}
///
/// Loads an intermediate from a WixOutput object.
///
/// WixOutput object.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(WixOutput wixOutput, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
return Intermediate.LoadIntermediate(wixOutput, creator, suppressVersionCheck);
}
///
/// Loads several intermediates from paths on disk using the same definitions.
///
/// Paths to intermediate files saved on disk.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediates
public static IEnumerable Load(IEnumerable intermediateFiles)
{
var creator = new SimpleTupleDefinitionCreator();
return Intermediate.Load(intermediateFiles, creator);
}
///
/// Loads several intermediates from paths on disk using the same definitions.
///
/// Paths to intermediate files saved on disk.
/// ITupleDefinitionCreator to use when reconstituting the intermediates.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediates
public static IEnumerable Load(IEnumerable intermediateFiles, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
var jsons = new Queue();
var intermediates = new List();
foreach (var path in intermediateFiles)
{
using (var wixout = WixOutput.Read(path))
{
var data = wixout.GetData(WixOutputStreamName);
var json = Intermediate.LoadJson(data, wixout.Uri, suppressVersionCheck);
Intermediate.LoadDefinitions(json, creator);
jsons.Enqueue(new JsonWithPath { Json = json, Path = wixout.Uri });
}
}
while (jsons.Count > 0)
{
var jsonWithPath = jsons.Dequeue();
var intermediate = Intermediate.FinalizeLoad(jsonWithPath.Json, jsonWithPath.Path, creator);
intermediates.Add(intermediate);
}
return intermediates;
}
///
/// Updates the intermediate level to the specified level.
///
/// Intermediate level.
public void UpdateLevel(string level)
{
this.Level = String.IsNullOrEmpty(this.Level) ? level : String.Concat(this.Level, ";", level);
}
///
/// Returns whether a specifed intermediate level has been set for this intermediate.
///
/// Intermediate level.
/// True if the specifed intermediate level has been set for this intermediate.
public bool HasLevel(string level)
{
return this.Level?.Contains(level) == true;
}
///
/// Saves an intermediate to a path on disk.
///
/// Path to save intermediate file to disk.
public void Save(string path)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
using (var wixout = WixOutput.Create(path))
{
this.Save(wixout);
}
}
///
/// Saves an intermediate to a path on disk.
///
/// Path to save intermediate file to disk.
public void Save(WixOutput wixout)
{
this.SaveEmbedFiles(wixout);
this.SaveIR(wixout);
}
///
/// Loads an intermediate from a path on disk.
///
/// Stream to intermediate file.
/// Path name of intermediate file.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
private static Intermediate LoadIntermediate(WixOutput wixout, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
var data = wixout.GetData(WixOutputStreamName);
var json = Intermediate.LoadJson(data, wixout.Uri, suppressVersionCheck);
Intermediate.LoadDefinitions(json, creator);
return Intermediate.FinalizeLoad(json, wixout.Uri, creator);
}
///
/// Loads json form of intermedaite from stream.
///
/// Stream to intermediate file.
/// Path name of intermediate file.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded json.
private static JsonObject LoadJson(string json, Uri baseUri, bool suppressVersionCheck)
{
var jsonObject = SimpleJson.DeserializeObject(json) as JsonObject;
if (!suppressVersionCheck)
{
var versionJson = jsonObject.GetValueOrDefault("version");
if (!Version.TryParse(versionJson, out var version) || !Intermediate.CurrentVersion.Equals(version))
{
throw new WixException(ErrorMessages.VersionMismatch(SourceLineNumber.CreateFromUri(baseUri.AbsoluteUri), "intermediate", versionJson, Intermediate.CurrentVersion.ToString()));
}
}
return jsonObject;
}
///
/// Loads custom definitions in intermediate json into the creator.
///
/// Json version of intermediate.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
private static void LoadDefinitions(JsonObject json, ITupleDefinitionCreator creator)
{
var definitionsJson = json.GetValueOrDefault("definitions");
if (definitionsJson != null)
{
foreach (JsonObject definitionJson in definitionsJson)
{
var definition = IntermediateTupleDefinition.Deserialize(definitionJson);
creator.AddCustomTupleDefinition(definition);
}
}
}
///
/// Loads the sections and localization for the intermediate.
///
/// Json version of intermediate.
/// Path to the intermediate.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// The finalized intermediate.
private static Intermediate FinalizeLoad(JsonObject json, Uri baseUri, ITupleDefinitionCreator creator)
{
var id = json.GetValueOrDefault("id");
var level = json.GetValueOrDefault("level");
var sections = new List();
var sectionsJson = json.GetValueOrDefault("sections");
foreach (JsonObject sectionJson in sectionsJson)
{
var section = IntermediateSection.Deserialize(creator, baseUri, sectionJson);
sections.Add(section);
}
var localizations = new Dictionary(StringComparer.OrdinalIgnoreCase);
var localizationsJson = json.GetValueOrDefault("localizations") ?? new JsonArray();
foreach (JsonObject localizationJson in localizationsJson)
{
var localization = Localization.Deserialize(localizationJson);
localizations.Add(localization.Culture, localization);
}
return new Intermediate(id, level, sections, localizations);
}
private void SaveEmbedFiles(WixOutput wixout)
{
var embeddedFields = this.Sections.SelectMany(s => s.Tuples)
.SelectMany(t => t.Fields)
.Where(f => f?.Type == IntermediateFieldType.Path)
.Select(f => f.AsPath())
.Where(f => f.Embed)
.ToList();
var savedEmbedFields = new Dictionary(StringComparer.OrdinalIgnoreCase);
var uniqueEntryNames = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (var embeddedField in embeddedFields)
{
var key = String.Concat(embeddedField.BaseUri?.AbsoluteUri, "?", embeddedField.Path);
if (savedEmbedFields.TryGetValue(key, out var existing))
{
embeddedField.Path = existing.Path;
}
else
{
var entryName = CalculateUniqueEntryName(uniqueEntryNames, embeddedField.Path);
if (embeddedField.BaseUri == null)
{
wixout.ImportDataStream(entryName, embeddedField.Path);
}
else // open the container specified in baseUri and copy the correct stream out of it.
{
using (var otherWixout = WixOutput.Read(embeddedField.BaseUri))
using (var stream = otherWixout.GetDataStream(embeddedField.Path))
using (var target = wixout.CreateDataStream(entryName))
{
stream.CopyTo(target);
}
}
embeddedField.Path = entryName;
savedEmbedFields.Add(key, embeddedField);
}
}
}
private void SaveIR(WixOutput wixout)
{
using (var writer = new StreamWriter(wixout.CreateDataStream(WixOutputStreamName)))
{
var jsonObject = new JsonObject
{
{ "id", this.Id },
{ "level", this.Level },
{ "version", Intermediate.CurrentVersion.ToString() }
};
var sectionsJson = new JsonArray(this.Sections.Count);
foreach (var section in this.Sections)
{
var sectionJson = section.Serialize();
sectionsJson.Add(sectionJson);
}
jsonObject.Add("sections", sectionsJson);
var customDefinitions = this.GetCustomDefinitionsInSections();
if (customDefinitions.Count > 0)
{
var customDefinitionsJson = new JsonArray(customDefinitions.Count);
foreach (var kvp in customDefinitions.OrderBy(d => d.Key))
{
var customDefinitionJson = kvp.Value.Serialize();
customDefinitionsJson.Add(customDefinitionJson);
}
jsonObject.Add("definitions", customDefinitionsJson);
}
if (this.Localizations.Any())
{
var localizationsJson = new JsonArray();
foreach (var localization in this.Localizations)
{
var localizationJson = localization.Serialize();
localizationsJson.Add(localizationJson);
}
jsonObject.Add("localizations", localizationsJson);
}
var json = SimpleJson.SerializeObject(jsonObject);
writer.Write(json);
}
}
private static string CalculateUniqueEntryName(ISet entryNames, string path)
{
var filename = Path.GetFileName(path);
var entryName = "wix-ir/" + filename;
var i = 0;
while (!entryNames.Add(entryName))
{
entryName = $"wix-ir/{filename}-{++i}";
}
return entryName;
}
private Dictionary GetCustomDefinitionsInSections()
{
var customDefinitions = new Dictionary();
foreach (var tuple in this.Sections.SelectMany(s => s.Tuples).Where(t => t.Definition.Type == TupleDefinitionType.MustBeFromAnExtension))
{
if (!customDefinitions.ContainsKey(tuple.Definition.Name))
{
customDefinitions.Add(tuple.Definition.Name, tuple.Definition);
}
}
return customDefinitions;
}
private struct JsonWithPath
{
public JsonObject Json { get; set; }
public Uri Path { get; set; }
}
}
}