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