// 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.Extensibility { using System; using System.Collections; using WixToolset.Data; using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility.Services; /// /// Base class for creating a validator extension. This default implementation /// will fire and event with the ICE name and description. /// public class ValidatorExtension { private string databaseFile; private Hashtable indexedSourceLineNumbers; private Output output; private SourceLineNumber sourceLineNumbers; private readonly IMessaging messaging; /// /// Instantiate a new . /// public ValidatorExtension(IMessaging messaging) { this.messaging = messaging; } /// /// Gets or sets the path to the database to validate. /// /// The path to the database to validate. public string DatabaseFile { get { return this.databaseFile; } set { this.databaseFile = value; } } /// /// Gets or sets the for finding source line information. /// /// The for finding source line information. public Output Output { get { return this.output; } set { this.output = value; } } /// /// Called at the beginning of the validation of a database file. /// /// /// The will set /// before calling InitializeValidator. /// Notes to Inheritors: When overriding /// InitializeValidator in a derived class, be sure to call /// the base class's InitializeValidator to thoroughly /// initialize the extension. /// public virtual void InitializeValidator() { if (this.databaseFile != null) { this.sourceLineNumbers = new SourceLineNumber(databaseFile); } } /// /// Called at the end of the validation of a database file. /// /// /// The default implementation will nullify source lines. /// Notes to Inheritors: When overriding /// FinalizeValidator in a derived class, be sure to call /// the base class's FinalizeValidator to thoroughly /// finalize the extension. /// public virtual void FinalizeValidator() { this.sourceLineNumbers = null; } /// /// Logs a message from the . /// /// A of tab-delmited tokens /// in the validation message. public virtual void Log(string message) { this.Log(message, null); } /// /// Logs a message from the . /// /// A of tab-delmited tokens /// in the validation message. /// The name of the action to which the message /// belongs. /// The message cannot be null. /// /// The message does not contain four (4) /// or more tab-delimited tokens. /// /// a tab-delimited set of tokens, /// formatted according to Windows Installer guidelines for ICE /// message. The following table lists what each token by index /// should mean. /// a name that represents the ICE /// action that was executed (e.g. 'ICE08'). /// /// /// Index /// Description /// /// /// 0 /// Name of the ICE. /// /// /// 1 /// Message type. See the following list. /// /// /// 2 /// Detailed description. /// /// /// 3 /// Help URL or location. /// /// /// 4 /// Table name. /// /// /// 5 /// Column name. /// /// /// 6 /// This and remaining fields are primary keys /// to identify a row. /// /// /// The message types are one of the following value. /// /// /// Value /// Message Type /// /// /// 0 /// Failure message reporting the failure of the /// ICE custom action. /// /// /// 1 /// Error message reporting database authoring that /// case incorrect behavior. /// /// /// 2 /// Warning message reporting database authoring that /// causes incorrect behavior in certain cases. Warnings can also /// report unexpected side-effects of database authoring. /// /// /// /// 3 /// Informational message. /// /// /// public virtual void Log(string message, string action) { if (message == null) { throw new ArgumentNullException("message"); } string[] messageParts = message.Split('\t'); if (3 > messageParts.Length) { if (null == action) { throw new WixException(ErrorMessages.UnexpectedExternalUIMessage(message)); } else { throw new WixException(ErrorMessages.UnexpectedExternalUIMessage(message, action)); } } SourceLineNumber messageSourceLineNumbers = null; if (6 < messageParts.Length) { string[] primaryKeys = new string[messageParts.Length - 6]; Array.Copy(messageParts, 6, primaryKeys, 0, primaryKeys.Length); messageSourceLineNumbers = this.GetSourceLineNumbers(messageParts[4], primaryKeys); } else // use the file name as the source line information { messageSourceLineNumbers = this.sourceLineNumbers; } switch (messageParts[1]) { case "0": case "1": this.messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, messageParts[0], messageParts[2])); break; case "2": this.messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, messageParts[0], messageParts[2])); break; case "3": this.messaging.Write(VerboseMessages.ValidationInfo(messageParts[0], messageParts[2])); break; default: throw new WixException(ErrorMessages.InvalidValidatorMessageType(messageParts[1])); } } /// /// Gets the source line information (if available) for a row by its table name and primary key. /// /// The table name of the row. /// The primary keys of the row. /// The source line number information if found; null otherwise. protected SourceLineNumber GetSourceLineNumbers(string tableName, string[] primaryKeys) { // source line information only exists if an output file was supplied if (null != this.output) { // index the source line information if it hasn't been indexed already if (null == this.indexedSourceLineNumbers) { this.indexedSourceLineNumbers = new Hashtable(); // index each real table foreach (Table table in this.output.Tables) { // skip unreal tables if (table.Definition.Unreal) { continue; } // index each row foreach (Row row in table.Rows) { // skip rows that don't contain source line information if (null == row.SourceLineNumbers) { continue; } // index the row using its table name and primary key string primaryKey = row.GetPrimaryKey(';'); if (null != primaryKey) { string key = String.Concat(table.Name, ":", primaryKey); if (this.indexedSourceLineNumbers.ContainsKey(key)) { this.messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); } else { this.indexedSourceLineNumbers.Add(key, row.SourceLineNumbers); } } } } } return (SourceLineNumber)this.indexedSourceLineNumbers[String.Concat(tableName, ":", String.Join(";", primaryKeys))]; } // use the file name as the source line information return this.sourceLineNumbers; } } }