diff options
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs')
-rw-r--r-- | src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs b/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs new file mode 100644 index 00000000..44ec3106 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/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 | |||
3 | namespace 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 | } | ||