aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Extensibility
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-09-17 15:35:20 -0700
committerRob Mensching <rob@firegiant.com>2017-09-17 16:00:11 -0700
commitd3d3649a68cb1fa589fdd987a6690dbd5d671f0d (patch)
tree44fe37ee352b7e3a355cc1e08b1d7d5988c14cc0 /src/WixToolset.Core/Extensibility
parenta62610d23d6feb98be3b1e529a4e81b19d77d9d8 (diff)
downloadwix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.gz
wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.bz2
wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.zip
Initial code commit
Diffstat (limited to 'src/WixToolset.Core/Extensibility')
-rw-r--r--src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs33
-rw-r--r--src/WixToolset.Core/Extensibility/HarvesterExtension.cs26
-rw-r--r--src/WixToolset.Core/Extensibility/HeatExtension.cs204
-rw-r--r--src/WixToolset.Core/Extensibility/IHarvesterCore.cs62
-rw-r--r--src/WixToolset.Core/Extensibility/IHeatCore.cs36
-rw-r--r--src/WixToolset.Core/Extensibility/MutatorExtension.cs198
-rw-r--r--src/WixToolset.Core/Extensibility/ValidatorExtension.cs299
7 files changed, 858 insertions, 0 deletions
diff --git a/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs b/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs
new file mode 100644
index 00000000..bb53e30c
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs
@@ -0,0 +1,33 @@
1// 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.
2
3namespace WixToolset.Tools
4{
5 using System;
6
7 /// <summary>
8 /// Represents a custom attribute for declaring the type to use
9 /// as the default heat extension in an assembly.
10 /// </summary>
11 public class AssemblyDefaultHeatExtensionAttribute : Attribute
12 {
13 private readonly Type extensionType;
14
15 /// <summary>
16 /// Instantiate a new AssemblyDefaultHeatExtensionAttribute.
17 /// </summary>
18 /// <param name="extensionType">The type of the default heat extension in an assembly.</param>
19 public AssemblyDefaultHeatExtensionAttribute(Type extensionType)
20 {
21 this.extensionType = extensionType;
22 }
23
24 /// <summary>
25 /// Gets the type of the default heat extension in an assembly.
26 /// </summary>
27 /// <value>The type of the default heat extension in an assembly.</value>
28 public Type ExtensionType
29 {
30 get { return this.extensionType; }
31 }
32 }
33}
diff --git a/src/WixToolset.Core/Extensibility/HarvesterExtension.cs b/src/WixToolset.Core/Extensibility/HarvesterExtension.cs
new file mode 100644
index 00000000..d8d0ab34
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/HarvesterExtension.cs
@@ -0,0 +1,26 @@
1// 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.
2
3namespace WixToolset
4{
5 using Wix = WixToolset.Data.Serialize;
6
7 /// <summary>
8 /// The base harvester extension. Any of these methods can be overridden to change
9 /// the behavior of the harvester.
10 /// </summary>
11 public abstract class HarvesterExtension
12 {
13 /// <summary>
14 /// Gets or sets the harvester core for the extension.
15 /// </summary>
16 /// <value>The harvester core for the extension.</value>
17 public IHarvesterCore Core { get; set; }
18
19 /// <summary>
20 /// Harvest a WiX document.
21 /// </summary>
22 /// <param name="argument">The argument for harvesting.</param>
23 /// <returns>The harvested Fragments.</returns>
24 public abstract Wix.Fragment[] Harvest(string argument);
25 }
26}
diff --git a/src/WixToolset.Core/Extensibility/HeatExtension.cs b/src/WixToolset.Core/Extensibility/HeatExtension.cs
new file mode 100644
index 00000000..5e292220
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/HeatExtension.cs
@@ -0,0 +1,204 @@
1// 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.
2
3namespace WixToolset.Extensibility
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Reflection;
9 using WixToolset;
10 using WixToolset.Data;
11 using WixToolset.Extensibilty;
12 using WixToolset.Tools;
13 using Wix = WixToolset.Data.Serialize;
14
15 /// <summary>
16 /// A command line option.
17 /// </summary>
18 public struct HeatCommandLineOption
19 {
20 public string Option;
21
22 public string Description;
23
24 /// <summary>
25 /// Instantiates a new CommandLineOption.
26 /// </summary>
27 /// <param name="option">The option name.</param>
28 /// <param name="description">The description of the option.</param>
29 public HeatCommandLineOption(string option, string description)
30 {
31 this.Option = option;
32 this.Description = description;
33 }
34 }
35
36 /// <summary>
37 /// An extension for the WiX Toolset Harvester application.
38 /// </summary>
39 public abstract class HeatExtension
40 {
41 /// <summary>
42 /// Gets or sets the heat core for the extension.
43 /// </summary>
44 /// <value>The heat core for the extension.</value>
45 public IHeatCore Core { get; set; }
46
47 /// <summary>
48 /// Gets the supported command line types for this extension.
49 /// </summary>
50 /// <value>The supported command line types for this extension.</value>
51 public virtual HeatCommandLineOption[] CommandLineTypes
52 {
53 get { return null; }
54 }
55
56 /// <summary>
57 /// Loads a HeatExtension from a type description string.
58 /// </summary>
59 /// <param name="extension">The extension type description string.</param>
60 /// <returns>The loaded HeatExtension.</returns>
61 /// <remarks>
62 /// <paramref name="extension"/> can be in several different forms:
63 /// <list type="number">
64 /// <item><term>AssemblyQualifiedName (TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089)</term></item>
65 /// <item><term>AssemblyName (MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089)</term></item>
66 /// <item><term>Absolute path to an assembly (C:\MyExtensions\ExtensionAssembly.dll)</term></item>
67 /// <item><term>Filename of an assembly in the application directory (ExtensionAssembly.dll)</term></item>
68 /// <item><term>Relative path to an assembly (..\..\MyExtensions\ExtensionAssembly.dll)</term></item>
69 /// </list>
70 /// To specify a particular class to use, prefix the fully qualified class name to the assembly and separate them with a comma.
71 /// For example: "TopNamespace.SubNameSpace.ContainingClass+NestedClass, C:\MyExtensions\ExtensionAssembly.dll"
72 /// </remarks>
73 public static HeatExtension Load(string extension)
74 {
75 Type extensionType = null;
76 int commaIndex = extension.IndexOf(',');
77 string className = String.Empty;
78 string assemblyName = extension;
79
80 if (0 <= commaIndex)
81 {
82 className = extension.Substring(0, commaIndex);
83 assemblyName = (extension.Length <= commaIndex + 1 ? String.Empty : extension.Substring(commaIndex + 1));
84 }
85
86 className = className.Trim();
87 assemblyName = assemblyName.Trim();
88
89 if (null == extensionType && 0 < assemblyName.Length)
90 {
91
92 Assembly extensionAssembly;
93
94 // case 3: Absolute path to an assembly
95 if (Path.IsPathRooted(assemblyName))
96 {
97 extensionAssembly = ExtensionLoadFrom(assemblyName);
98 }
99 else
100 {
101 try
102 {
103 // case 2: AssemblyName
104 extensionAssembly = Assembly.Load(assemblyName);
105 }
106 catch (IOException e)
107 {
108 if (e is FileLoadException || e is FileNotFoundException)
109 {
110 try
111 {
112 // case 4: Filename of an assembly in the application directory
113 extensionAssembly = Assembly.Load(Path.GetFileNameWithoutExtension(assemblyName));
114 }
115 catch (IOException innerE)
116 {
117 if (innerE is FileLoadException || innerE is FileNotFoundException)
118 {
119 // case 5: Relative path to an assembly
120
121 // we want to use Assembly.Load when we can because it has some benefits over Assembly.LoadFrom
122 // (see the documentation for Assembly.LoadFrom). However, it may fail when the path is a relative
123 // path, so we should try Assembly.LoadFrom one last time. We could have detected a directory
124 // separator character and used Assembly.LoadFrom directly, but dealing with path canonicalization
125 // issues is something we don't want to deal with if we don't have to.
126 extensionAssembly = ExtensionLoadFrom(assemblyName);
127 }
128 else
129 {
130 throw new WixException(WixErrors.InvalidExtension(assemblyName, innerE.Message));
131 }
132 }
133 }
134 else
135 {
136 throw new WixException(WixErrors.InvalidExtension(assemblyName, e.Message));
137 }
138 }
139 }
140
141 if (0 < className.Length)
142 {
143 try
144 {
145 // case 1: AssemblyQualifiedName
146 extensionType = extensionAssembly.GetType(className, true /* throwOnError */, true /* ignoreCase */);
147 }
148 catch (Exception e)
149 {
150 throw new WixException(WixErrors.InvalidExtensionType(assemblyName, className, e.GetType().ToString(), e.Message));
151 }
152 }
153 else
154 {
155 // if no class name was specified, then let's hope the assembly defined a default WixExtension
156 AssemblyDefaultHeatExtensionAttribute extensionAttribute = (AssemblyDefaultHeatExtensionAttribute)Attribute.GetCustomAttribute(extensionAssembly, typeof(AssemblyDefaultHeatExtensionAttribute));
157
158 if (null != extensionAttribute)
159 {
160 extensionType = extensionAttribute.ExtensionType;
161 }
162 else
163 {
164 throw new WixException(WixErrors.InvalidExtensionType(assemblyName, typeof(AssemblyDefaultHeatExtensionAttribute).ToString()));
165 }
166 }
167 }
168
169 if (extensionType.IsSubclassOf(typeof(HeatExtension)))
170 {
171 return Activator.CreateInstance(extensionType) as HeatExtension;
172 }
173 else
174 {
175 throw new WixException(WixErrors.InvalidExtensionType(extension, extensionType.ToString(), typeof(HeatExtension).ToString()));
176 }
177 }
178
179 /// <summary>
180 /// Parse the command line options for this extension.
181 /// </summary>
182 /// <param name="type">The active harvester type.</param>
183 /// <param name="args">The option arguments.</param>
184 public virtual void ParseOptions(string type, string[] args)
185 {
186 }
187
188 private static Assembly ExtensionLoadFrom(string assemblyName)
189 {
190 Assembly extensionAssembly = null;
191
192 try
193 {
194 extensionAssembly = Assembly.LoadFrom(assemblyName);
195 }
196 catch (Exception e)
197 {
198 throw new WixException(WixErrors.InvalidExtension(assemblyName, e.Message));
199 }
200
201 return extensionAssembly;
202 }
203 }
204}
diff --git a/src/WixToolset.Core/Extensibility/IHarvesterCore.cs b/src/WixToolset.Core/Extensibility/IHarvesterCore.cs
new file mode 100644
index 00000000..9a6fd10c
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/IHarvesterCore.cs
@@ -0,0 +1,62 @@
1// 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.
2
3namespace WixToolset
4{
5 using System;
6 using System.Diagnostics.CodeAnalysis;
7 using System.IO;
8 using WixToolset.Data;
9 using Wix = WixToolset.Data.Serialize;
10
11 /// <summary>
12 /// The WiX Toolset harvester core.
13 /// </summary>
14 public interface IHarvesterCore
15 {
16 /// <summary>
17 /// Gets whether the harvester core encountered an error while processing.
18 /// </summary>
19 /// <value>Flag if core encountered an error during processing.</value>
20 bool EncounteredError { get; }
21
22 /// <summary>
23 /// Gets or sets the value of the extension argument passed to heat.
24 /// </summary>
25 /// <value>The extension argument.</value>
26 string ExtensionArgument { get; set; }
27
28 /// <summary>
29 /// Gets or sets the value of the root directory that is being harvested.
30 /// </summary>
31 /// <value>The root directory being harvested.</value>
32 string RootDirectory { get; set; }
33
34 /// <summary>
35 /// Create an identifier based on passed file name
36 /// </summary>
37 /// <param name="name">File name to generate identifer from</param>
38 /// <returns></returns>
39 string CreateIdentifierFromFilename(string filename);
40
41 /// <summary>
42 /// Generate an identifier by hashing data from the row.
43 /// </summary>
44 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
45 /// <param name="args">Information to hash.</param>
46 /// <returns>The generated identifier.</returns>
47 string GenerateIdentifier(string prefix, params string[] args);
48
49 /// <summary>
50 /// Sends a message to the message delegate if there is one.
51 /// </summary>
52 /// <param name="mea">Message event arguments.</param>
53 void OnMessage(MessageEventArgs mea);
54
55 /// <summary>
56 /// Resolves a file's path if the Wix.File.Source value starts with "SourceDir\".
57 /// </summary>
58 /// <param name="fileSource">The Wix.File.Source value with "SourceDir\".</param>
59 /// <returns>The full path of the file.</returns>
60 string ResolveFilePath(string fileSource);
61 }
62}
diff --git a/src/WixToolset.Core/Extensibility/IHeatCore.cs b/src/WixToolset.Core/Extensibility/IHeatCore.cs
new file mode 100644
index 00000000..bc853b24
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/IHeatCore.cs
@@ -0,0 +1,36 @@
1// 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.
2
3namespace WixToolset.Extensibilty
4{
5 using WixToolset.Data;
6
7 /// <summary>
8 /// The WiX Toolset Harvester application core.
9 /// </summary>
10 public interface IHeatCore
11 {
12 /// <summary>
13 /// Gets whether the mutator core encountered an error while processing.
14 /// </summary>
15 /// <value>Flag if core encountered an error during processing.</value>
16 bool EncounteredError { get; }
17
18 /// <summary>
19 /// Gets the harvester.
20 /// </summary>
21 /// <value>The harvester.</value>
22 Harvester Harvester { get; }
23
24 /// <summary>
25 /// Gets the mutator.
26 /// </summary>
27 /// <value>The mutator.</value>
28 Mutator Mutator { get; }
29
30 /// <summary>
31 /// Sends a message to the message delegate if there is one.
32 /// </summary>
33 /// <param name="mea">Message event arguments.</param>
34 void OnMessage(MessageEventArgs mea);
35 }
36}
diff --git a/src/WixToolset.Core/Extensibility/MutatorExtension.cs b/src/WixToolset.Core/Extensibility/MutatorExtension.cs
new file mode 100644
index 00000000..9de64180
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/MutatorExtension.cs
@@ -0,0 +1,198 @@
1// 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.
2
3namespace WixToolset.Extensibility
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Text;
9
10 using Wix = WixToolset.Data.Serialize;
11
12 /// <summary>
13 /// The base mutator extension. Any of these methods can be overridden to change
14 /// the behavior of the mutator.
15 /// </summary>
16 public abstract class MutatorExtension
17 {
18 /// <summary>
19 /// Gets or sets the mutator core for the extension.
20 /// </summary>
21 /// <value>The mutator core for the extension.</value>
22 public IHarvesterCore Core { get; set; }
23
24 /// <summary>
25 /// Gets the sequence of the extension.
26 /// </summary>
27 /// <value>The sequence of the extension.</value>
28 public abstract int Sequence
29 {
30 get;
31 }
32
33 /// <summary>
34 /// Mutate a WiX document.
35 /// </summary>
36 /// <param name="wix">The Wix document element.</param>
37 public virtual void Mutate(Wix.Wix wix)
38 {
39 }
40
41 /// <summary>
42 /// Mutate a WiX document as a string.
43 /// </summary>
44 /// <param name="wix">The Wix document element as a string.</param>
45 /// <returns>The mutated Wix document as a string.</returns>
46 public virtual string Mutate(string wixString)
47 {
48 return wixString;
49 }
50
51 /// <summary>
52 /// Generate unique MSI identifiers.
53 /// </summary>
54 protected class IdentifierGenerator
55 {
56 public const int MaxProductIdentifierLength = 72;
57 public const int MaxModuleIdentifierLength = 35;
58
59 private string baseName;
60 private int maxLength;
61 private Dictionary<string, object> existingIdentifiers;
62 private Dictionary<string, object> possibleIdentifiers;
63
64 /// <summary>
65 /// Instantiate a new IdentifierGenerator.
66 /// </summary>
67 /// <param name="baseName">The base resource name to use if a resource name contains no usable characters.</param>
68 public IdentifierGenerator(string baseName)
69 {
70 this.baseName = baseName;
71 this.maxLength = IdentifierGenerator.MaxProductIdentifierLength;
72 this.existingIdentifiers = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
73 this.possibleIdentifiers = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
74 }
75
76 /// <summary>
77 /// Gets or sets the maximum length for generated identifiers.
78 /// </summary>
79 /// <value>Maximum length for generated identifiers. (Default is 72.)</value>
80 public int MaxIdentifierLength
81 {
82 get { return this.maxLength; }
83 set { this.maxLength = value; }
84 }
85
86 /// <summary>
87 /// Index an existing identifier for collision detection.
88 /// </summary>
89 /// <param name="identifier">The identifier.</param>
90 public void IndexExistingIdentifier(string identifier)
91 {
92 if (null == identifier)
93 {
94 throw new ArgumentNullException("identifier");
95 }
96
97 this.existingIdentifiers[identifier] = null;
98 }
99
100 /// <summary>
101 /// Index a resource name for collision detection.
102 /// </summary>
103 /// <param name="name">The resource name.</param>
104 public void IndexName(string name)
105 {
106 if (null == name)
107 {
108 throw new ArgumentNullException("name");
109 }
110
111 string identifier = this.CreateIdentifier(name, 0);
112
113 if (this.possibleIdentifiers.ContainsKey(identifier))
114 {
115 this.possibleIdentifiers[identifier] = String.Empty;
116 }
117 else
118 {
119 this.possibleIdentifiers.Add(identifier, null);
120 }
121 }
122
123 /// <summary>
124 /// Get the identifier for the given resource name.
125 /// </summary>
126 /// <param name="name">The resource name.</param>
127 /// <returns>A legal MSI identifier.</returns>
128 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
129 public string GetIdentifier(string name)
130 {
131 if (null == name)
132 {
133 throw new ArgumentNullException("name");
134 }
135
136 for (int i = 0; i <= Int32.MaxValue; i++)
137 {
138 string identifier = this.CreateIdentifier(name, i);
139
140 if (this.existingIdentifiers.ContainsKey(identifier) || // already used
141 (0 == i && 0 != this.possibleIdentifiers.Count && null != this.possibleIdentifiers[identifier]) || // needs an index because its duplicated
142 (0 != i && this.possibleIdentifiers.ContainsKey(identifier))) // collides with another possible identifier
143 {
144 continue;
145 }
146 else // use this identifier
147 {
148 this.existingIdentifiers.Add(identifier, null);
149
150 return identifier;
151 }
152 }
153
154 throw new InvalidOperationException(WixStrings.EXP_CouldnotFileUniqueIDForResourceName);
155 }
156
157 /// <summary>
158 /// Create a legal MSI identifier from a resource name and an index.
159 /// </summary>
160 /// <param name="name">The name of the resource for which an identifier should be created.</param>
161 /// <param name="index">An index to append to the end of the identifier to make it unique.</param>
162 /// <returns>A legal MSI identifier.</returns>
163 public string CreateIdentifier(string name, int index)
164 {
165 if (null == name)
166 {
167 throw new ArgumentNullException("name");
168 }
169
170 StringBuilder identifier = new StringBuilder();
171
172 // Convert the name to a standard MSI identifier
173 identifier.Append(Common.GetIdentifierFromName(name));
174
175 // no legal identifier characters were found, use the base id instead
176 if (0 == identifier.Length)
177 {
178 identifier.Append(this.baseName);
179 }
180
181 // truncate the identifier if it's too long (reserve 3 characters for up to 99 collisions)
182 int adjustedMaxLength = this.MaxIdentifierLength - (index != 0 ? 3 : 0);
183 if (adjustedMaxLength < identifier.Length)
184 {
185 identifier.Length = adjustedMaxLength;
186 }
187
188 // if the index is not zero, then append it to the identifier name
189 if (0 != index)
190 {
191 identifier.AppendFormat("_{0}", index);
192 }
193
194 return identifier.ToString();
195 }
196 }
197 }
198}
diff --git a/src/WixToolset.Core/Extensibility/ValidatorExtension.cs b/src/WixToolset.Core/Extensibility/ValidatorExtension.cs
new file mode 100644
index 00000000..44ec3106
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/ValidatorExtension.cs
@@ -0,0 +1,299 @@
1// 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.
2
3namespace WixToolset.Extensibility
4{
5 using System;
6 using System.Collections;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Base class for creating a validator extension. This default implementation
11 /// will fire and event with the ICE name and description.
12 /// </summary>
13 public class ValidatorExtension : IMessageHandler
14 {
15 private string databaseFile;
16 private Hashtable indexedSourceLineNumbers;
17 private Output output;
18 private SourceLineNumber sourceLineNumbers;
19
20 /// <summary>
21 /// Instantiate a new <see cref="ValidatorExtension"/>.
22 /// </summary>
23 public ValidatorExtension()
24 {
25 }
26
27 /// <summary>
28 /// Gets or sets the path to the database to validate.
29 /// </summary>
30 /// <value>The path to the database to validate.</value>
31 public string DatabaseFile
32 {
33 get { return this.databaseFile; }
34 set { this.databaseFile = value; }
35 }
36
37 /// <summary>
38 /// Gets or sets the <see cref="Output"/> for finding source line information.
39 /// </summary>
40 /// <value>The <see cref="Output"/> for finding source line information.</value>
41 public Output Output
42 {
43 get { return this.output; }
44 set { this.output = value; }
45 }
46
47 /// <summary>
48 /// Called at the beginning of the validation of a database file.
49 /// </summary>
50 /// <remarks>
51 /// <para>The <see cref="Validator"/> will set
52 /// <see cref="DatabaseFile"/> before calling InitializeValidator.</para>
53 /// <para><b>Notes to Inheritors:</b> When overriding
54 /// <b>InitializeValidator</b> in a derived class, be sure to call
55 /// the base class's <b>InitializeValidator</b> to thoroughly
56 /// initialize the extension.</para>
57 /// </remarks>
58 public virtual void InitializeValidator()
59 {
60 if (this.databaseFile != null)
61 {
62 this.sourceLineNumbers = new SourceLineNumber(databaseFile);
63 }
64 }
65
66 /// <summary>
67 /// Called at the end of the validation of a database file.
68 /// </summary>
69 /// <remarks>
70 /// <para>The default implementation will nullify source lines.</para>
71 /// <para><b>Notes to Inheritors:</b> When overriding
72 /// <b>FinalizeValidator</b> in a derived class, be sure to call
73 /// the base class's <b>FinalizeValidator</b> to thoroughly
74 /// finalize the extension.</para>
75 /// </remarks>
76 public virtual void FinalizeValidator()
77 {
78 this.sourceLineNumbers = null;
79 }
80
81 /// <summary>
82 /// Logs a message from the <see cref="Validator"/>.
83 /// </summary>
84 /// <param name="message">A <see cref="String"/> of tab-delmited tokens
85 /// in the validation message.</param>
86 public virtual void Log(string message)
87 {
88 this.Log(message, null);
89 }
90
91 /// <summary>
92 /// Logs a message from the <see cref="Validator"/>.
93 /// </summary>
94 /// <param name="message">A <see cref="String"/> of tab-delmited tokens
95 /// in the validation message.</param>
96 /// <param name="action">The name of the action to which the message
97 /// belongs.</param>
98 /// <exception cref="ArgumentNullException">The message cannot be null.
99 /// </exception>
100 /// <exception cref="WixException">The message does not contain four (4)
101 /// or more tab-delimited tokens.</exception>
102 /// <remarks>
103 /// <para><paramref name="message"/> a tab-delimited set of tokens,
104 /// formatted according to Windows Installer guidelines for ICE
105 /// message. The following table lists what each token by index
106 /// should mean.</para>
107 /// <para><paramref name="action"/> a name that represents the ICE
108 /// action that was executed (e.g. 'ICE08').</para>
109 /// <list type="table">
110 /// <listheader>
111 /// <term>Index</term>
112 /// <description>Description</description>
113 /// </listheader>
114 /// <item>
115 /// <term>0</term>
116 /// <description>Name of the ICE.</description>
117 /// </item>
118 /// <item>
119 /// <term>1</term>
120 /// <description>Message type. See the following list.</description>
121 /// </item>
122 /// <item>
123 /// <term>2</term>
124 /// <description>Detailed description.</description>
125 /// </item>
126 /// <item>
127 /// <term>3</term>
128 /// <description>Help URL or location.</description>
129 /// </item>
130 /// <item>
131 /// <term>4</term>
132 /// <description>Table name.</description>
133 /// </item>
134 /// <item>
135 /// <term>5</term>
136 /// <description>Column name.</description>
137 /// </item>
138 /// <item>
139 /// <term>6</term>
140 /// <description>This and remaining fields are primary keys
141 /// to identify a row.</description>
142 /// </item>
143 /// </list>
144 /// <para>The message types are one of the following value.</para>
145 /// <list type="table">
146 /// <listheader>
147 /// <term>Value</term>
148 /// <description>Message Type</description>
149 /// </listheader>
150 /// <item>
151 /// <term>0</term>
152 /// <description>Failure message reporting the failure of the
153 /// ICE custom action.</description>
154 /// </item>
155 /// <item>
156 /// <term>1</term>
157 /// <description>Error message reporting database authoring that
158 /// case incorrect behavior.</description>
159 /// </item>
160 /// <item>
161 /// <term>2</term>
162 /// <description>Warning message reporting database authoring that
163 /// causes incorrect behavior in certain cases. Warnings can also
164 /// report unexpected side-effects of database authoring.
165 /// </description>
166 /// </item>
167 /// <item>
168 /// <term>3</term>
169 /// <description>Informational message.</description>
170 /// </item>
171 /// </list>
172 /// </remarks>
173 public virtual void Log(string message, string action)
174 {
175 if (message == null)
176 {
177 throw new ArgumentNullException("message");
178 }
179
180 string[] messageParts = message.Split('\t');
181 if (3 > messageParts.Length)
182 {
183 if (null == action)
184 {
185 throw new WixException(WixErrors.UnexpectedExternalUIMessage(message));
186 }
187 else
188 {
189 throw new WixException(WixErrors.UnexpectedExternalUIMessage(message, action));
190 }
191 }
192
193 SourceLineNumber messageSourceLineNumbers = null;
194 if (6 < messageParts.Length)
195 {
196 string[] primaryKeys = new string[messageParts.Length - 6];
197
198 Array.Copy(messageParts, 6, primaryKeys, 0, primaryKeys.Length);
199
200 messageSourceLineNumbers = this.GetSourceLineNumbers(messageParts[4], primaryKeys);
201 }
202 else // use the file name as the source line information
203 {
204 messageSourceLineNumbers = this.sourceLineNumbers;
205 }
206
207 switch (messageParts[1])
208 {
209 case "0":
210 case "1":
211 this.OnMessage(WixErrors.ValidationError(messageSourceLineNumbers, messageParts[0], messageParts[2]));
212 break;
213 case "2":
214 this.OnMessage(WixWarnings.ValidationWarning(messageSourceLineNumbers, messageParts[0], messageParts[2]));
215 break;
216 case "3":
217 this.OnMessage(WixVerboses.ValidationInfo(messageParts[0], messageParts[2]));
218 break;
219 default:
220 throw new WixException(WixErrors.InvalidValidatorMessageType(messageParts[1]));
221 }
222 }
223
224 /// <summary>
225 /// Gets the source line information (if available) for a row by its table name and primary key.
226 /// </summary>
227 /// <param name="tableName">The table name of the row.</param>
228 /// <param name="primaryKeys">The primary keys of the row.</param>
229 /// <returns>The source line number information if found; null otherwise.</returns>
230 protected SourceLineNumber GetSourceLineNumbers(string tableName, string[] primaryKeys)
231 {
232 // source line information only exists if an output file was supplied
233 if (null != this.output)
234 {
235 // index the source line information if it hasn't been indexed already
236 if (null == this.indexedSourceLineNumbers)
237 {
238 this.indexedSourceLineNumbers = new Hashtable();
239
240 // index each real table
241 foreach (Table table in this.output.Tables)
242 {
243 // skip unreal tables
244 if (table.Definition.Unreal)
245 {
246 continue;
247 }
248
249 // index each row
250 foreach (Row row in table.Rows)
251 {
252 // skip rows that don't contain source line information
253 if (null == row.SourceLineNumbers)
254 {
255 continue;
256 }
257
258 // index the row using its table name and primary key
259 string primaryKey = row.GetPrimaryKey(';');
260 if (null != primaryKey)
261 {
262 string key = String.Concat(table.Name, ":", primaryKey);
263
264 if (this.indexedSourceLineNumbers.ContainsKey(key))
265 {
266 this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name));
267 }
268 else
269 {
270 this.indexedSourceLineNumbers.Add(key, row.SourceLineNumbers);
271 }
272 }
273 }
274 }
275 }
276
277 return (SourceLineNumber)this.indexedSourceLineNumbers[String.Concat(tableName, ":", String.Join(";", primaryKeys))];
278 }
279
280 // use the file name as the source line information
281 return this.sourceLineNumbers;
282 }
283
284 /// <summary>
285 /// Sends a message to the <see cref="Message"/> delegate if there is one.
286 /// </summary>
287 /// <param name="e">Message event arguments.</param>
288 /// <remarks>
289 /// <para><b>Notes to Inheritors:</b> When overriding <b>OnMessage</b>
290 /// in a derived class, be sure to call the base class's
291 /// <b>OnMessage</b> method so that registered delegates recieve
292 /// the event.</para>
293 /// </remarks>
294 public virtual void OnMessage(MessageEventArgs e)
295 {
296 Messaging.Instance.OnMessage(e);
297 }
298 }
299}