diff options
Diffstat (limited to 'src/WixToolset.Core/Link/WixGroupingOrdering.cs')
-rw-r--r-- | src/WixToolset.Core/Link/WixGroupingOrdering.cs | 726 |
1 files changed, 726 insertions, 0 deletions
diff --git a/src/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/WixToolset.Core/Link/WixGroupingOrdering.cs new file mode 100644 index 00000000..fc0ce43b --- /dev/null +++ b/src/WixToolset.Core/Link/WixGroupingOrdering.cs | |||
@@ -0,0 +1,726 @@ | |||
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.Link | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.ObjectModel; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Diagnostics; | ||
10 | using System.Globalization; | ||
11 | using System.Linq; | ||
12 | using System.Text; | ||
13 | using WixToolset.Extensibility; | ||
14 | using WixToolset.Data; | ||
15 | |||
16 | /// <summary> | ||
17 | /// Grouping and Ordering class of the WiX toolset. | ||
18 | /// </summary> | ||
19 | internal sealed class WixGroupingOrdering : IMessageHandler | ||
20 | { | ||
21 | private Output output; | ||
22 | private IMessageHandler messageHandler; | ||
23 | private List<string> groupTypes; | ||
24 | private List<string> itemTypes; | ||
25 | private ItemCollection items; | ||
26 | private List<int> rowsUsed; | ||
27 | private bool loaded; | ||
28 | private bool encounteredError; | ||
29 | |||
30 | /// <summary> | ||
31 | /// Creates a WixGroupingOrdering object. | ||
32 | /// </summary> | ||
33 | /// <param name="output">Output from which to read the group and order information.</param> | ||
34 | /// <param name="messageHandler">Handler for any error messages.</param> | ||
35 | /// <param name="groupTypes">Group types to include.</param> | ||
36 | /// <param name="itemTypes">Item types to include.</param> | ||
37 | public WixGroupingOrdering(Output output, IMessageHandler messageHandler) | ||
38 | { | ||
39 | this.output = output; | ||
40 | this.messageHandler = messageHandler; | ||
41 | |||
42 | this.rowsUsed = new List<int>(); | ||
43 | this.loaded = false; | ||
44 | this.encounteredError = false; | ||
45 | } | ||
46 | |||
47 | /// <summary> | ||
48 | /// Switches a WixGroupingOrdering object to operate on a new set of groups/items. | ||
49 | /// </summary> | ||
50 | /// <param name="groupTypes">Group types to include.</param> | ||
51 | /// <param name="itemTypes">Item types to include.</param> | ||
52 | public void UseTypes(IEnumerable<string> groupTypes, IEnumerable<string> itemTypes) | ||
53 | { | ||
54 | this.groupTypes = new List<string>(groupTypes); | ||
55 | this.itemTypes = new List<string>(itemTypes); | ||
56 | |||
57 | this.items = new ItemCollection(); | ||
58 | this.loaded = false; | ||
59 | } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Sends a message to the message handler if there is one. | ||
63 | /// </summary> | ||
64 | /// <param name="mea">Message event arguments.</param> | ||
65 | public void OnMessage(MessageEventArgs e) | ||
66 | { | ||
67 | WixErrorEventArgs errorEventArgs = e as WixErrorEventArgs; | ||
68 | |||
69 | if (null != errorEventArgs || MessageLevel.Error == e.Level) | ||
70 | { | ||
71 | this.encounteredError = true; | ||
72 | } | ||
73 | |||
74 | if (null != this.messageHandler) | ||
75 | { | ||
76 | this.messageHandler.OnMessage(e); | ||
77 | } | ||
78 | else if (null != errorEventArgs) | ||
79 | { | ||
80 | throw new WixException(errorEventArgs); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | /// <summary> | ||
85 | /// Finds all nested items under a parent group and creates new WixGroup data for them. | ||
86 | /// </summary> | ||
87 | /// <param name="parentType">The group type for the parent group to flatten.</param> | ||
88 | /// <param name="parentId">The identifier of the parent group to flatten.</param> | ||
89 | /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param> | ||
90 | public void FlattenAndRewriteRows(string parentType, string parentId, bool removeUsedRows) | ||
91 | { | ||
92 | Debug.Assert(this.groupTypes.Contains(parentType)); | ||
93 | |||
94 | List<Item> orderedItems; | ||
95 | this.CreateOrderedList(parentType, parentId, out orderedItems); | ||
96 | if (this.encounteredError) | ||
97 | { | ||
98 | return; | ||
99 | } | ||
100 | |||
101 | this.CreateNewGroupRows(parentType, parentId, orderedItems); | ||
102 | |||
103 | if (removeUsedRows) | ||
104 | { | ||
105 | this.RemoveUsedGroupRows(); | ||
106 | } | ||
107 | } | ||
108 | |||
109 | /// <summary> | ||
110 | /// Finds all items under a parent group type and creates new WixGroup data for them. | ||
111 | /// </summary> | ||
112 | /// <param name="parentType">The type of the parent group to flatten.</param> | ||
113 | /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param> | ||
114 | public void FlattenAndRewriteGroups(string parentType, bool removeUsedRows) | ||
115 | { | ||
116 | Debug.Assert(this.groupTypes.Contains(parentType)); | ||
117 | |||
118 | this.LoadFlattenOrderGroups(); | ||
119 | if (this.encounteredError) | ||
120 | { | ||
121 | return; | ||
122 | } | ||
123 | |||
124 | foreach (Item item in this.items) | ||
125 | { | ||
126 | if (parentType == item.Type) | ||
127 | { | ||
128 | List<Item> orderedItems; | ||
129 | this.CreateOrderedList(item.Type, item.Id, out orderedItems); | ||
130 | this.CreateNewGroupRows(item.Type, item.Id, orderedItems); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | if (removeUsedRows) | ||
135 | { | ||
136 | this.RemoveUsedGroupRows(); | ||
137 | } | ||
138 | } | ||
139 | |||
140 | |||
141 | /// <summary> | ||
142 | /// Creates a flattened and ordered list of items for the given parent group. | ||
143 | /// </summary> | ||
144 | /// <param name="parentType">The group type for the parent group to flatten.</param> | ||
145 | /// <param name="parentId">The identifier of the parent group to flatten.</param> | ||
146 | /// <param name="orderedItems">The returned list of ordered items.</param> | ||
147 | private void CreateOrderedList(string parentType, string parentId, out List<Item> orderedItems) | ||
148 | { | ||
149 | orderedItems = null; | ||
150 | |||
151 | this.LoadFlattenOrderGroups(); | ||
152 | if (this.encounteredError) | ||
153 | { | ||
154 | return; | ||
155 | } | ||
156 | |||
157 | Item parentItem; | ||
158 | if (!this.items.TryGetValue(parentType, parentId, out parentItem)) | ||
159 | { | ||
160 | this.OnMessage(WixErrors.IdentifierNotFound(parentType, parentId)); | ||
161 | return; | ||
162 | } | ||
163 | |||
164 | orderedItems = new List<Item>(parentItem.ChildItems); | ||
165 | orderedItems.Sort(new Item.AfterItemComparer()); | ||
166 | } | ||
167 | |||
168 | /// <summary> | ||
169 | /// Removes rows from WixGroup that have been used by this object. | ||
170 | /// </summary> | ||
171 | public void RemoveUsedGroupRows() | ||
172 | { | ||
173 | List<int> sortedIndexes = this.rowsUsed.Distinct().OrderByDescending(i => i).ToList(); | ||
174 | |||
175 | Table wixGroupTable = this.output.Tables["WixGroup"]; | ||
176 | Debug.Assert(null != wixGroupTable); | ||
177 | Debug.Assert(sortedIndexes[0] < wixGroupTable.Rows.Count); | ||
178 | |||
179 | foreach (int rowIndex in sortedIndexes) | ||
180 | { | ||
181 | wixGroupTable.Rows.RemoveAt(rowIndex); | ||
182 | } | ||
183 | } | ||
184 | |||
185 | /// <summary> | ||
186 | /// Creates new WixGroup rows for a list of items. | ||
187 | /// </summary> | ||
188 | /// <param name="parentType">The group type for the parent group in the new rows.</param> | ||
189 | /// <param name="parentId">The identifier of the parent group in the new rows.</param> | ||
190 | /// <param name="orderedItems">The list of new items.</param> | ||
191 | private void CreateNewGroupRows(string parentType, string parentId, List<Item> orderedItems) | ||
192 | { | ||
193 | // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither | ||
194 | // does WiX (although they do, currently). We probably want to "upgrade" this to a new | ||
195 | // table that includes a sequence number, and then change the code that uses ordered | ||
196 | // groups to read from that table instead. | ||
197 | Table wixGroupTable = this.output.Tables["WixGroup"]; | ||
198 | Debug.Assert(null != wixGroupTable); | ||
199 | |||
200 | foreach (Item item in orderedItems) | ||
201 | { | ||
202 | Row row = wixGroupTable.CreateRow(item.Row.SourceLineNumbers); | ||
203 | row[0] = parentId; | ||
204 | row[1] = parentType; | ||
205 | row[2] = item.Id; | ||
206 | row[3] = item.Type; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | // Group/Ordering Flattening Logic | ||
211 | // | ||
212 | // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in | ||
213 | // play: grouping (parent/child relationships) and ordering (before/after relationships). | ||
214 | // Dealing with just one or the other is straghtforward. Groups can be flattened | ||
215 | // recursively. Ordering can be propagated in either direction. When the ordering also | ||
216 | // participates in the grouping constructions, however, things get trickier. For the | ||
217 | // purposes of this discussion, we're dealing with "items" and "groups", and an instance | ||
218 | // of either of them can be marked as coming "after" some other instance. | ||
219 | // | ||
220 | // For simple item-to-item ordering, the "after" values simply propagate: if A is after B, | ||
221 | // and B is after C, then we can say that A is after *both* B and C. If a group is involved, | ||
222 | // it acts as a proxy for all of its included items and any sub-groups. | ||
223 | |||
224 | /// <summary> | ||
225 | /// Internal workhorse for ensuring that group and ordering information has | ||
226 | /// been loaded and applied. | ||
227 | /// </summary> | ||
228 | private void LoadFlattenOrderGroups() | ||
229 | { | ||
230 | if (!this.loaded) | ||
231 | { | ||
232 | this.LoadGroups(); | ||
233 | this.LoadOrdering(); | ||
234 | |||
235 | // It would be really nice to have a "find circular after dependencies" | ||
236 | // function, but it gets much more complicated because of the way that | ||
237 | // the dependencies are propagated across group boundaries. For now, we | ||
238 | // just live with the dependency loop detection as we flatten the | ||
239 | // dependencies. Group references, however, we can check directly. | ||
240 | this.FindCircularGroupReferences(); | ||
241 | |||
242 | if (!this.encounteredError) | ||
243 | { | ||
244 | this.FlattenGroups(); | ||
245 | this.FlattenOrdering(); | ||
246 | } | ||
247 | |||
248 | this.loaded = true; | ||
249 | } | ||
250 | } | ||
251 | |||
252 | /// <summary> | ||
253 | /// Loads data from the WixGroup table. | ||
254 | /// </summary> | ||
255 | private void LoadGroups() | ||
256 | { | ||
257 | Table wixGroupTable = this.output.Tables["WixGroup"]; | ||
258 | if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count) | ||
259 | { | ||
260 | // TODO: Change message name to make it *not* Bundle specific? | ||
261 | this.OnMessage(WixErrors.MissingBundleInformation("WixGroup")); | ||
262 | } | ||
263 | |||
264 | // Collect all of the groups | ||
265 | for (int rowIndex = 0; rowIndex < wixGroupTable.Rows.Count; ++rowIndex) | ||
266 | { | ||
267 | Row row = wixGroupTable.Rows[rowIndex]; | ||
268 | string rowParentName = (string)row[0]; | ||
269 | string rowParentType = (string)row[1]; | ||
270 | string rowChildName = (string)row[2]; | ||
271 | string rowChildType = (string)row[3]; | ||
272 | |||
273 | // If this row specifies a parent or child type that's not in our | ||
274 | // lists, we assume it's not a row that we're concerned about. | ||
275 | if (!this.groupTypes.Contains(rowParentType) || | ||
276 | !this.itemTypes.Contains(rowChildType)) | ||
277 | { | ||
278 | continue; | ||
279 | } | ||
280 | |||
281 | this.rowsUsed.Add(rowIndex); | ||
282 | |||
283 | Item parentItem; | ||
284 | if (!this.items.TryGetValue(rowParentType, rowParentName, out parentItem)) | ||
285 | { | ||
286 | parentItem = new Item(row, rowParentType, rowParentName); | ||
287 | this.items.Add(parentItem); | ||
288 | } | ||
289 | |||
290 | Item childItem; | ||
291 | if (!this.items.TryGetValue(rowChildType, rowChildName, out childItem)) | ||
292 | { | ||
293 | childItem = new Item(row, rowChildType, rowChildName); | ||
294 | this.items.Add(childItem); | ||
295 | } | ||
296 | |||
297 | parentItem.ChildItems.Add(childItem); | ||
298 | } | ||
299 | } | ||
300 | |||
301 | /// <summary> | ||
302 | /// Flattens group/item information. | ||
303 | /// </summary> | ||
304 | private void FlattenGroups() | ||
305 | { | ||
306 | foreach (Item item in this.items) | ||
307 | { | ||
308 | item.FlattenChildItems(); | ||
309 | } | ||
310 | } | ||
311 | |||
312 | /// <summary> | ||
313 | /// Finds and reports circular references in the group/item data. | ||
314 | /// </summary> | ||
315 | private void FindCircularGroupReferences() | ||
316 | { | ||
317 | ItemCollection itemsInKnownLoops = new ItemCollection(); | ||
318 | foreach (Item item in this.items) | ||
319 | { | ||
320 | if (itemsInKnownLoops.Contains(item)) | ||
321 | { | ||
322 | continue; | ||
323 | } | ||
324 | |||
325 | ItemCollection itemsSeen = new ItemCollection(); | ||
326 | string circularReference; | ||
327 | if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) | ||
328 | { | ||
329 | itemsInKnownLoops.Add(itemsSeen); | ||
330 | this.OnMessage(WixErrors.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); | ||
331 | } | ||
332 | } | ||
333 | } | ||
334 | |||
335 | /// <summary> | ||
336 | /// Recursive worker to find and report circular references in group/item data. | ||
337 | /// </summary> | ||
338 | /// <param name="checkItem">The sentinal item being checked.</param> | ||
339 | /// <param name="currentItem">The current item in the recursion.</param> | ||
340 | /// <param name="itemsSeen">A list of all items already visited (for performance).</param> | ||
341 | /// <param name="circularReference">A list of items in the current circular reference, if one was found; null otherwise.</param> | ||
342 | /// <returns>True if a circular reference was found; false otherwise.</returns> | ||
343 | private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference) | ||
344 | { | ||
345 | circularReference = null; | ||
346 | foreach (Item subitem in currentItem.ChildItems) | ||
347 | { | ||
348 | if (checkItem == subitem) | ||
349 | { | ||
350 | // TODO: Even better would be to include the source lines for each reference! | ||
351 | circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}", | ||
352 | currentItem.Type, currentItem.Id, subitem.Type, subitem.Id); | ||
353 | return true; | ||
354 | } | ||
355 | |||
356 | if (!itemsSeen.Contains(subitem)) | ||
357 | { | ||
358 | itemsSeen.Add(subitem); | ||
359 | if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference)) | ||
360 | { | ||
361 | // TODO: Even better would be to include the source lines for each reference! | ||
362 | circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}", | ||
363 | currentItem.Type, currentItem.Id, circularReference); | ||
364 | return true; | ||
365 | } | ||
366 | } | ||
367 | } | ||
368 | |||
369 | return false; | ||
370 | } | ||
371 | |||
372 | /// <summary> | ||
373 | /// Loads ordering dependency data from the WixOrdering table. | ||
374 | /// </summary> | ||
375 | private void LoadOrdering() | ||
376 | { | ||
377 | Table wixOrderingTable = output.Tables["WixOrdering"]; | ||
378 | if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count) | ||
379 | { | ||
380 | // TODO: Do we need a message here? | ||
381 | return; | ||
382 | } | ||
383 | |||
384 | foreach (Row row in wixOrderingTable.Rows) | ||
385 | { | ||
386 | string rowItemType = (string)row[0]; | ||
387 | string rowItemName = (string)row[1]; | ||
388 | string rowDependsOnType = (string)row[2]; | ||
389 | string rowDependsOnName = (string)row[3]; | ||
390 | |||
391 | // If this row specifies some other (unknown) type in either | ||
392 | // position, we assume it's not a row that we're concerned about. | ||
393 | // For ordering, we allow group and item in either position. | ||
394 | if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) || | ||
395 | !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType))) | ||
396 | { | ||
397 | continue; | ||
398 | } | ||
399 | |||
400 | Item item = null; | ||
401 | Item dependsOn = null; | ||
402 | |||
403 | if (!this.items.TryGetValue(rowItemType, rowItemName, out item)) | ||
404 | { | ||
405 | this.OnMessage(WixErrors.IdentifierNotFound(rowItemType, rowItemName)); | ||
406 | } | ||
407 | |||
408 | if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out dependsOn)) | ||
409 | { | ||
410 | this.OnMessage(WixErrors.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); | ||
411 | } | ||
412 | |||
413 | if (null == item || null == dependsOn) | ||
414 | { | ||
415 | continue; | ||
416 | } | ||
417 | |||
418 | item.AddAfter(dependsOn, this); | ||
419 | } | ||
420 | } | ||
421 | |||
422 | /// <summary> | ||
423 | /// Flattens the ordering dependencies in the groups/items. | ||
424 | /// </summary> | ||
425 | private void FlattenOrdering() | ||
426 | { | ||
427 | // Because items don't know about their parent groups (and can, in fact, be | ||
428 | // in more than one group at a time), we need to pre-propagate the 'afters' | ||
429 | // from each parent item to its children before we attempt to flatten the | ||
430 | // ordering. | ||
431 | foreach (Item item in this.items) | ||
432 | { | ||
433 | item.PropagateAfterToChildItems(this); | ||
434 | } | ||
435 | |||
436 | foreach (Item item in this.items) | ||
437 | { | ||
438 | item.FlattenAfters(this); | ||
439 | } | ||
440 | } | ||
441 | |||
442 | /// <summary> | ||
443 | /// A variant of KeyedCollection that doesn't throw when an item is re-added. | ||
444 | /// </summary> | ||
445 | /// <typeparam name="TKey">Key type for the collection.</typeparam> | ||
446 | /// <typeparam name="TItem">Item type for the colelction.</typeparam> | ||
447 | internal abstract class EnhancedKeyCollection<TKey, TItem> : KeyedCollection<TKey, TItem> | ||
448 | { | ||
449 | new public void Add(TItem item) | ||
450 | { | ||
451 | if (!this.Contains(item)) | ||
452 | { | ||
453 | base.Add(item); | ||
454 | } | ||
455 | } | ||
456 | |||
457 | public void Add(Collection<TItem> list) | ||
458 | { | ||
459 | foreach (TItem item in list) | ||
460 | { | ||
461 | this.Add(item); | ||
462 | } | ||
463 | } | ||
464 | |||
465 | public void Remove(Collection<TItem> list) | ||
466 | { | ||
467 | foreach (TItem item in list) | ||
468 | { | ||
469 | this.Remove(item); | ||
470 | } | ||
471 | } | ||
472 | |||
473 | public bool TryGetValue(TKey key, out TItem item) | ||
474 | { | ||
475 | // KeyedCollection doesn't implement the TryGetValue() method, but it's | ||
476 | // a useful concept. We can't just always pass this to the enclosed | ||
477 | // Dictionary, however, because it doesn't always exist! If it does, we | ||
478 | // can delegate to it as one would expect. If it doesn't, we have to | ||
479 | // implement everything ourselves in terms of Contains(). | ||
480 | |||
481 | if (null != this.Dictionary) | ||
482 | { | ||
483 | return this.Dictionary.TryGetValue(key, out item); | ||
484 | } | ||
485 | |||
486 | if (this.Contains(key)) | ||
487 | { | ||
488 | item = this[key]; | ||
489 | return true; | ||
490 | } | ||
491 | |||
492 | item = default(TItem); | ||
493 | return false; | ||
494 | } | ||
495 | |||
496 | #if DEBUG | ||
497 | // This just makes debugging easier... | ||
498 | public override string ToString() | ||
499 | { | ||
500 | StringBuilder sb = new StringBuilder(); | ||
501 | foreach (TItem item in this) | ||
502 | { | ||
503 | sb.AppendFormat("{0}, ", item); | ||
504 | } | ||
505 | sb.Length -= 2; | ||
506 | return sb.ToString(); | ||
507 | } | ||
508 | #endif // DEBUG | ||
509 | } | ||
510 | |||
511 | /// <summary> | ||
512 | /// A specialized EnhancedKeyCollection, typed to Items. | ||
513 | /// </summary> | ||
514 | internal class ItemCollection : EnhancedKeyCollection<string, Item> | ||
515 | { | ||
516 | protected override string GetKeyForItem(Item item) | ||
517 | { | ||
518 | return item.Key; | ||
519 | } | ||
520 | |||
521 | public bool TryGetValue(string type, string id, out Item item) | ||
522 | { | ||
523 | return this.TryGetValue(CreateKeyFromTypeId(type, id), out item); | ||
524 | } | ||
525 | |||
526 | public static string CreateKeyFromTypeId(string type, string id) | ||
527 | { | ||
528 | return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id); | ||
529 | } | ||
530 | } | ||
531 | |||
532 | /// <summary> | ||
533 | /// An item (or group) in the grouping/ordering engine. | ||
534 | /// </summary> | ||
535 | /// <remarks>Encapsulates nested group membership and also before/after | ||
536 | /// ordering dependencies.</remarks> | ||
537 | internal class Item | ||
538 | { | ||
539 | private ItemCollection afterItems; | ||
540 | private ItemCollection beforeItems; // for checking for circular references | ||
541 | private bool flattenedAfterItems; | ||
542 | |||
543 | public Item(Row row, string type, string id) | ||
544 | { | ||
545 | this.Row = row; | ||
546 | this.Type = type; | ||
547 | this.Id = id; | ||
548 | |||
549 | this.Key = ItemCollection.CreateKeyFromTypeId(type, id); | ||
550 | |||
551 | afterItems = new ItemCollection(); | ||
552 | beforeItems = new ItemCollection(); | ||
553 | flattenedAfterItems = false; | ||
554 | } | ||
555 | |||
556 | public Row Row { get; private set; } | ||
557 | public string Type { get; private set; } | ||
558 | public string Id { get; private set; } | ||
559 | public string Key { get; private set; } | ||
560 | |||
561 | #if DEBUG | ||
562 | // Makes debugging easier... | ||
563 | public override string ToString() | ||
564 | { | ||
565 | return this.Key; | ||
566 | } | ||
567 | #endif // DEBUG | ||
568 | |||
569 | private ItemCollection childItems = new ItemCollection(); | ||
570 | public ItemCollection ChildItems { get { return childItems; } } | ||
571 | |||
572 | /// <summary> | ||
573 | /// Removes any nested groups under this item and replaces | ||
574 | /// them with their child items. | ||
575 | /// </summary> | ||
576 | public void FlattenChildItems() | ||
577 | { | ||
578 | ItemCollection flattenedChildItems = new ItemCollection(); | ||
579 | |||
580 | foreach (Item childItem in this.ChildItems) | ||
581 | { | ||
582 | if (0 == childItem.ChildItems.Count) | ||
583 | { | ||
584 | flattenedChildItems.Add(childItem); | ||
585 | } | ||
586 | else | ||
587 | { | ||
588 | childItem.FlattenChildItems(); | ||
589 | flattenedChildItems.Add(childItem.ChildItems); | ||
590 | } | ||
591 | } | ||
592 | |||
593 | this.ChildItems.Clear(); | ||
594 | this.ChildItems.Add(flattenedChildItems); | ||
595 | } | ||
596 | |||
597 | /// <summary> | ||
598 | /// Adds a list of items to the 'after' ordering collection. | ||
599 | /// </summary> | ||
600 | /// <param name="items">List of items to add.</param> | ||
601 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
602 | public void AddAfter(ItemCollection items, IMessageHandler messageHandler) | ||
603 | { | ||
604 | foreach (Item item in items) | ||
605 | { | ||
606 | this.AddAfter(item, messageHandler); | ||
607 | } | ||
608 | } | ||
609 | |||
610 | /// <summary> | ||
611 | /// Adds an item to the 'after' ordering collection. | ||
612 | /// </summary> | ||
613 | /// <param name="item">Items to add.</param> | ||
614 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
615 | public void AddAfter(Item after, IMessageHandler messageHandler) | ||
616 | { | ||
617 | if (this.beforeItems.Contains(after)) | ||
618 | { | ||
619 | // We could try to chain this up (the way that group circular dependencies | ||
620 | // are reported), but since we're in the process of flattening, we may already | ||
621 | // have lost some distinction between authored and propagated ordering. | ||
622 | string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}", | ||
623 | this.Type, this.Id, after.Type, after.Id); | ||
624 | messageHandler.OnMessage(WixErrors.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference)); | ||
625 | return; | ||
626 | } | ||
627 | |||
628 | this.afterItems.Add(after); | ||
629 | after.beforeItems.Add(this); | ||
630 | } | ||
631 | |||
632 | /// <summary> | ||
633 | /// Propagates 'after' dependencies from an item to its child items. | ||
634 | /// </summary> | ||
635 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
636 | /// <remarks>Because items don't know about their parent groups (and can, in fact, be in more | ||
637 | /// than one group at a time), we need to propagate the 'afters' from each parent item to its children | ||
638 | /// before we attempt to flatten the ordering.</remarks> | ||
639 | public void PropagateAfterToChildItems(IMessageHandler messageHandler) | ||
640 | { | ||
641 | if (this.ShouldItemPropagateChildOrdering()) | ||
642 | { | ||
643 | foreach (Item childItem in this.childItems) | ||
644 | { | ||
645 | childItem.AddAfter(this.afterItems, messageHandler); | ||
646 | } | ||
647 | } | ||
648 | } | ||
649 | |||
650 | /// <summary> | ||
651 | /// Flattens the ordering dependency for this item. | ||
652 | /// </summary> | ||
653 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
654 | public void FlattenAfters(IMessageHandler messageHandler) | ||
655 | { | ||
656 | if (this.flattenedAfterItems) | ||
657 | { | ||
658 | return; | ||
659 | } | ||
660 | |||
661 | this.flattenedAfterItems = true; | ||
662 | |||
663 | // Ensure that if we're after something (A), and *it's* after something (B), | ||
664 | // that we list ourselved as after both (A) *and* (B). | ||
665 | ItemCollection nestedAfterItems = new ItemCollection(); | ||
666 | |||
667 | foreach (Item afterItem in this.afterItems) | ||
668 | { | ||
669 | afterItem.FlattenAfters(messageHandler); | ||
670 | nestedAfterItems.Add(afterItem.afterItems); | ||
671 | |||
672 | if (afterItem.ShouldItemPropagateChildOrdering()) | ||
673 | { | ||
674 | // If we are after a group, it really means | ||
675 | // we are after all of the group's children. | ||
676 | foreach (Item childItem in afterItem.ChildItems) | ||
677 | { | ||
678 | childItem.FlattenAfters(messageHandler); | ||
679 | nestedAfterItems.Add(childItem.afterItems); | ||
680 | nestedAfterItems.Add(childItem); | ||
681 | } | ||
682 | } | ||
683 | } | ||
684 | |||
685 | this.AddAfter(nestedAfterItems, messageHandler); | ||
686 | } | ||
687 | |||
688 | // We *don't* propagate ordering information from Packages or | ||
689 | // Containers to their children, because ordering doesn't matter | ||
690 | // for them, and a Payload in two Packages (or Containers) can | ||
691 | // cause a circular reference to occur. We do, however, need to | ||
692 | // track the ordering in the UX Container, because we need the | ||
693 | // first payload to be the entrypoint. | ||
694 | private bool ShouldItemPropagateChildOrdering() | ||
695 | { | ||
696 | if (String.Equals("Package", this.Type, StringComparison.Ordinal) || | ||
697 | (String.Equals("Container", this.Type, StringComparison.Ordinal) && | ||
698 | !String.Equals(Compiler.BurnUXContainerId, this.Id, StringComparison.Ordinal))) | ||
699 | { | ||
700 | return false; | ||
701 | } | ||
702 | return true; | ||
703 | } | ||
704 | |||
705 | /// <summary> | ||
706 | /// Helper IComparer class to make ordering easier. | ||
707 | /// </summary> | ||
708 | internal sealed class AfterItemComparer : IComparer<Item> | ||
709 | { | ||
710 | public int Compare(Item x, Item y) | ||
711 | { | ||
712 | if (x.afterItems.Contains(y)) | ||
713 | { | ||
714 | return 1; | ||
715 | } | ||
716 | else if (y.afterItems.Contains(x)) | ||
717 | { | ||
718 | return -1; | ||
719 | } | ||
720 | |||
721 | return string.CompareOrdinal(x.Id, y.Id); | ||
722 | } | ||
723 | } | ||
724 | } | ||
725 | } | ||
726 | } | ||