aboutsummaryrefslogtreecommitdiff
path: root/src/tools/heat/Serialize/ElementCollection.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-07-26 17:20:39 -0700
committerRob Mensching <rob@firegiant.com>2022-08-01 20:25:19 -0700
commita627ca9b720047e633a8fe72003ab9bee31006c5 (patch)
tree2bc8a924bb4141ab718e74d08f6459a0ffe8d573 /src/tools/heat/Serialize/ElementCollection.cs
parent521eb3c9cf38823a2c4019abb85dc0b3200b92cb (diff)
downloadwix-a627ca9b720047e633a8fe72003ab9bee31006c5.tar.gz
wix-a627ca9b720047e633a8fe72003ab9bee31006c5.tar.bz2
wix-a627ca9b720047e633a8fe72003ab9bee31006c5.zip
Create WixToolset.Heat.nupkg to distribute heat.exe and Heat targets
Moves Heat functionality to the "tools" layer and packages it all up in WixToolset.Heat.nupkg for distribution in WiX v4. Completes 6838
Diffstat (limited to 'src/tools/heat/Serialize/ElementCollection.cs')
-rw-r--r--src/tools/heat/Serialize/ElementCollection.cs618
1 files changed, 618 insertions, 0 deletions
diff --git a/src/tools/heat/Serialize/ElementCollection.cs b/src/tools/heat/Serialize/ElementCollection.cs
new file mode 100644
index 00000000..bacbafbd
--- /dev/null
+++ b/src/tools/heat/Serialize/ElementCollection.cs
@@ -0,0 +1,618 @@
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.Harvesters.Serialize
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 using WixToolset.Harvesters.Extensibility.Serialize;
9
10 /// <summary>
11 /// Collection used in the CodeDOM for the children of a given element. Provides type-checking
12 /// on the allowed children to ensure that only allowed types are added.
13 /// </summary>
14 public class ElementCollection : ICollection, IEnumerable
15 {
16 private CollectionType collectionType;
17 private int totalContainedItems;
18 private int containersUsed;
19 private ArrayList items;
20
21 /// <summary>
22 /// Creates a new element collection.
23 /// </summary>
24 /// <param name="collectionType">Type of the collection to create.</param>
25 public ElementCollection(CollectionType collectionType)
26 {
27 this.collectionType = collectionType;
28 this.items = new ArrayList();
29 }
30
31 /// <summary>
32 /// Enum representing types of XML collections.
33 /// </summary>
34 public enum CollectionType
35 {
36 /// <summary>
37 /// A choice type, corresponding to the XSD choice element.
38 /// </summary>
39 Choice,
40
41 /// <summary>
42 /// A sequence type, corresponding to the XSD sequence element.
43 /// </summary>
44 Sequence
45 }
46
47 /// <summary>
48 /// Gets the type of collection.
49 /// </summary>
50 /// <value>The type of collection.</value>
51 public CollectionType Type
52 {
53 get { return this.collectionType; }
54 }
55
56 /// <summary>
57 /// Gets the count of child elements in this collection (counts ISchemaElements, not nested collections).
58 /// </summary>
59 /// <value>The count of child elements in this collection (counts ISchemaElements, not nested collections).</value>
60 public int Count
61 {
62 get { return this.totalContainedItems; }
63 }
64
65 /// <summary>
66 /// Gets the flag specifying whether this collection is synchronized. Always returns false.
67 /// </summary>
68 /// <value>The flag specifying whether this collection is synchronized. Always returns false.</value>
69 public bool IsSynchronized
70 {
71 get { return false; }
72 }
73
74 /// <summary>
75 /// Gets an object external callers can synchronize on.
76 /// </summary>
77 /// <value>An object external callers can synchronize on.</value>
78 public object SyncRoot
79 {
80 get { return this; }
81 }
82
83 /// <summary>
84 /// Adds a child element to this collection.
85 /// </summary>
86 /// <param name="element">The element to add.</param>
87 /// <exception cref="ArgumentException">Thrown if the child is not of an allowed type.</exception>
88 public void AddElement(ISchemaElement element)
89 {
90 foreach (object obj in this.items)
91 {
92 bool containerUsed;
93
94 CollectionItem collectionItem = obj as CollectionItem;
95 if (collectionItem != null)
96 {
97 containerUsed = collectionItem.Elements.Count != 0;
98 if (collectionItem.ElementType.IsAssignableFrom(element.GetType()))
99 {
100 collectionItem.AddElement(element);
101
102 if (!containerUsed)
103 {
104 this.containersUsed++;
105 }
106
107 this.totalContainedItems++;
108 return;
109 }
110
111 continue;
112 }
113
114 ElementCollection collection = obj as ElementCollection;
115 if (collection != null)
116 {
117 containerUsed = collection.Count != 0;
118
119 try
120 {
121 collection.AddElement(element);
122
123 if (!containerUsed)
124 {
125 this.containersUsed++;
126 }
127
128 this.totalContainedItems++;
129 return;
130 }
131 catch (ArgumentException)
132 {
133 // Eat the exception and keep looking. We'll throw our own if we can't find its home.
134 }
135
136 continue;
137 }
138 }
139
140 throw new ArgumentException(String.Format(
141 CultureInfo.InvariantCulture,
142 WixHarvesterStrings.EXP_ElementOfTypeIsNotValidForThisCollection,
143 element.GetType().Name));
144 }
145
146 /// <summary>
147 /// Removes a child element from this collection.
148 /// </summary>
149 /// <param name="element">The element to remove.</param>
150 /// <exception cref="ArgumentException">Thrown if the element is not of an allowed type.</exception>
151 public void RemoveElement(ISchemaElement element)
152 {
153 foreach (object obj in this.items)
154 {
155 CollectionItem collectionItem = obj as CollectionItem;
156 if (collectionItem != null)
157 {
158 if (collectionItem.ElementType.IsAssignableFrom(element.GetType()))
159 {
160 if (collectionItem.Elements.Count == 0)
161 {
162 return;
163 }
164
165 collectionItem.RemoveElement(element);
166
167 if (collectionItem.Elements.Count == 0)
168 {
169 this.containersUsed--;
170 }
171
172 this.totalContainedItems--;
173 return;
174 }
175
176 continue;
177 }
178
179 ElementCollection collection = obj as ElementCollection;
180 if (collection != null)
181 {
182 if (collection.Count == 0)
183 {
184 continue;
185 }
186
187 try
188 {
189 collection.RemoveElement(element);
190
191 if (collection.Count == 0)
192 {
193 this.containersUsed--;
194 }
195
196 this.totalContainedItems--;
197 return;
198 }
199 catch (ArgumentException)
200 {
201 // Eat the exception and keep looking. We'll throw our own if we can't find its home.
202 }
203
204 continue;
205 }
206 }
207
208 throw new ArgumentException(String.Format(
209 CultureInfo.InvariantCulture,
210 WixHarvesterStrings.EXP_ElementOfTypeIsNotValidForThisCollection,
211 element.GetType().Name));
212 }
213
214 /// <summary>
215 /// Copies this collection to an array.
216 /// </summary>
217 /// <param name="array">Array to copy to.</param>
218 /// <param name="index">Offset into the array.</param>
219 public void CopyTo(Array array, int index)
220 {
221 int item = 0;
222 foreach (ISchemaElement element in this)
223 {
224 array.SetValue(element, (long)(item + index));
225 item++;
226 }
227 }
228
229 /// <summary>
230 /// Creates an enumerator for walking the elements in this collection.
231 /// </summary>
232 /// <returns>A newly created enumerator.</returns>
233 public IEnumerator GetEnumerator()
234 {
235 return new ElementCollectionEnumerator(this);
236 }
237
238 /// <summary>
239 /// Gets an enumerable collection of children of a given type.
240 /// </summary>
241 /// <param name="childType">Type of children to get.</param>
242 /// <returns>A collection of children.</returns>
243 /// <exception cref="ArgumentException">Thrown if the type isn't a valid child type.</exception>
244 public IEnumerable Filter(Type childType)
245 {
246 foreach (object container in this.items)
247 {
248 CollectionItem collectionItem = container as CollectionItem;
249 if (collectionItem != null)
250 {
251 if (collectionItem.ElementType.IsAssignableFrom(childType))
252 {
253 return collectionItem.Elements;
254 }
255
256 continue;
257 }
258
259 ElementCollection elementCollection = container as ElementCollection;
260 if (elementCollection != null)
261 {
262 IEnumerable nestedFilter = elementCollection.Filter(childType);
263 if (nestedFilter != null)
264 {
265 return nestedFilter;
266 }
267
268 continue;
269 }
270 }
271
272 throw new ArgumentException(String.Format(
273 CultureInfo.InvariantCulture,
274 WixHarvesterStrings.EXP_TypeIsNotValidForThisCollection,
275 childType.Name));
276 }
277
278 /// <summary>
279 /// Adds a type to this collection.
280 /// </summary>
281 /// <param name="collectionItem">CollectionItem representing the type to add.</param>
282 public void AddItem(CollectionItem collectionItem)
283 {
284 this.items.Add(collectionItem);
285 }
286
287 /// <summary>
288 /// Adds a nested collection to this collection.
289 /// </summary>
290 /// <param name="collection">ElementCollection to add.</param>
291 public void AddCollection(ElementCollection collection)
292 {
293 this.items.Add(collection);
294 }
295
296 /// <summary>
297 /// Class used to represent a given type in the child collection of an element. Abstract,
298 /// has subclasses for choice and sequence (which can do cardinality checks).
299 /// </summary>
300 public abstract class CollectionItem
301 {
302 private Type elementType;
303 private ArrayList elements;
304
305 /// <summary>
306 /// Creates a new CollectionItem for the given element type.
307 /// </summary>
308 /// <param name="elementType">Type of the element for this collection item.</param>
309 protected CollectionItem(Type elementType)
310 {
311 this.elementType = elementType;
312 this.elements = new ArrayList();
313 }
314
315 /// <summary>
316 /// Gets the type of this collection's items.
317 /// </summary>
318 /// <value>The type of this collection's items.</value>
319 public Type ElementType
320 {
321 get { return this.elementType; }
322 }
323
324 /// <summary>
325 /// Gets the elements of this collection.
326 /// </summary>
327 /// <value>The elements of this collection.</value>
328 public ArrayList Elements
329 {
330 get { return this.elements; }
331 }
332
333 /// <summary>
334 /// Adds an element to this collection. Must be of an assignable type to the collection's
335 /// type.
336 /// </summary>
337 /// <param name="element">The element to add.</param>
338 /// <exception cref="ArgumentException">Thrown if the type isn't assignable to the collection's type.</exception>
339 public void AddElement(ISchemaElement element)
340 {
341 if (!this.elementType.IsAssignableFrom(element.GetType()))
342 {
343 throw new ArgumentException(
344 String.Format(
345 CultureInfo.InvariantCulture,
346 WixHarvesterStrings.EXP_ElementIsSubclassOfDifferentType,
347 this.elementType.Name,
348 element.GetType().Name),
349 "element");
350 }
351
352 this.elements.Add(element);
353 }
354
355 /// <summary>
356 /// Removes an element from this collection.
357 /// </summary>
358 /// <param name="element">The element to remove.</param>
359 /// <exception cref="ArgumentException">Thrown if the element's type isn't assignable to the collection's type.</exception>
360 public void RemoveElement(ISchemaElement element)
361 {
362 if (!this.elementType.IsAssignableFrom(element.GetType()))
363 {
364 throw new ArgumentException(
365 String.Format(
366 CultureInfo.InvariantCulture,
367 WixHarvesterStrings.EXP_ElementIsSubclassOfDifferentType,
368 this.elementType.Name,
369 element.GetType().Name),
370 "element");
371 }
372
373 this.elements.Remove(element);
374 }
375 }
376
377 /// <summary>
378 /// Class representing a choice item. Doesn't do cardinality checks.
379 /// </summary>
380 public class ChoiceItem : CollectionItem
381 {
382 /// <summary>
383 /// Creates a new choice item.
384 /// </summary>
385 /// <param name="elementType">Type of the created item.</param>
386 public ChoiceItem(Type elementType)
387 : base(elementType)
388 {
389 }
390 }
391
392 /// <summary>
393 /// Class representing a sequence item. Can do cardinality checks, if required.
394 /// </summary>
395 public class SequenceItem : CollectionItem
396 {
397 /// <summary>
398 /// Creates a new sequence item.
399 /// </summary>
400 /// <param name="elementType">Type of the created item.</param>
401 public SequenceItem(Type elementType)
402 : base(elementType)
403 {
404 }
405 }
406
407 /// <summary>
408 /// Enumerator for the ElementCollection.
409 /// </summary>
410 private class ElementCollectionEnumerator : IEnumerator
411 {
412 private ElementCollection collection;
413 private Stack collectionStack;
414
415 /// <summary>
416 /// Creates a new ElementCollectionEnumerator.
417 /// </summary>
418 /// <param name="collection">The collection to create an enumerator for.</param>
419 public ElementCollectionEnumerator(ElementCollection collection)
420 {
421 this.collection = collection;
422 }
423
424 /// <summary>
425 /// Gets the current object from the enumerator.
426 /// </summary>
427 public object Current
428 {
429 get
430 {
431 if (this.collectionStack != null && this.collectionStack.Count > 0)
432 {
433 CollectionSymbol symbol = (CollectionSymbol)this.collectionStack.Peek();
434 object container = symbol.Collection.items[symbol.ContainerIndex];
435
436 CollectionItem collectionItem = container as CollectionItem;
437 if (collectionItem != null)
438 {
439 return collectionItem.Elements[symbol.ItemIndex];
440 }
441
442 throw new InvalidOperationException(String.Format(
443 CultureInfo.InvariantCulture,
444 WixHarvesterStrings.EXP_ElementMustBeChoiceItemOrSequenceItem,
445 container.GetType().Name));
446 }
447
448 return null;
449 }
450 }
451
452 /// <summary>
453 /// Resets the enumerator to the beginning.
454 /// </summary>
455 public void Reset()
456 {
457 if (this.collectionStack != null)
458 {
459 this.collectionStack.Clear();
460 this.collectionStack = null;
461 }
462 }
463
464 /// <summary>
465 /// Moves the enumerator to the next item.
466 /// </summary>
467 /// <returns>True if there is a next item, false otherwise.</returns>
468 public bool MoveNext()
469 {
470 if (this.collectionStack == null)
471 {
472 if (this.collection.Count == 0)
473 {
474 return false;
475 }
476
477 this.collectionStack = new Stack();
478 this.collectionStack.Push(new CollectionSymbol(this.collection));
479 }
480
481 CollectionSymbol symbol = (CollectionSymbol)this.collectionStack.Peek();
482
483 if (this.FindNext(symbol))
484 {
485 return true;
486 }
487
488 this.collectionStack.Pop();
489 if (this.collectionStack.Count == 0)
490 {
491 return false;
492 }
493
494 return this.MoveNext();
495 }
496
497 /// <summary>
498 /// Pushes a collection onto the stack.
499 /// </summary>
500 /// <param name="elementCollection">The collection to push.</param>
501 private void PushCollection(ElementCollection elementCollection)
502 {
503 if (elementCollection.Count <= 0)
504 {
505 throw new ArgumentException(String.Format(
506 CultureInfo.InvariantCulture,
507 WixHarvesterStrings.EXP_CollectionMustHaveAtLeastOneElement,
508 elementCollection.Count));
509 }
510
511 CollectionSymbol symbol = new CollectionSymbol(elementCollection);
512 this.collectionStack.Push(symbol);
513 this.FindNext(symbol);
514 }
515
516 /// <summary>
517 /// Finds the next item from a given symbol.
518 /// </summary>
519 /// <param name="symbol">The symbol to start looking from.</param>
520 /// <returns>True if a next element is found, false otherwise.</returns>
521 private bool FindNext(CollectionSymbol symbol)
522 {
523 object container = symbol.Collection.items[symbol.ContainerIndex];
524
525 CollectionItem collectionItem = container as CollectionItem;
526 if (collectionItem != null)
527 {
528 if (symbol.ItemIndex + 1 < collectionItem.Elements.Count)
529 {
530 symbol.ItemIndex++;
531 return true;
532 }
533 }
534
535 ElementCollection elementCollection = container as ElementCollection;
536 if (elementCollection != null && elementCollection.Count > 0 && symbol.ItemIndex == -1)
537 {
538 symbol.ItemIndex++;
539 this.PushCollection(elementCollection);
540 return true;
541 }
542
543 symbol.ItemIndex = 0;
544
545 for (int i = symbol.ContainerIndex + 1; i < symbol.Collection.items.Count; ++i)
546 {
547 object nestedContainer = symbol.Collection.items[i];
548
549 CollectionItem nestedCollectionItem = nestedContainer as CollectionItem;
550 if (nestedCollectionItem != null)
551 {
552 if (nestedCollectionItem.Elements.Count > 0)
553 {
554 symbol.ContainerIndex = i;
555 return true;
556 }
557 }
558
559 ElementCollection nestedElementCollection = nestedContainer as ElementCollection;
560 if (nestedElementCollection != null && nestedElementCollection.Count > 0)
561 {
562 symbol.ContainerIndex = i;
563 this.PushCollection(nestedElementCollection);
564 return true;
565 }
566 }
567
568 return false;
569 }
570
571 /// <summary>
572 /// Class representing a single point in the collection. Consists of an ElementCollection,
573 /// a container index, and an index into the container.
574 /// </summary>
575 private class CollectionSymbol
576 {
577 private ElementCollection collection;
578 private int containerIndex;
579 private int itemIndex = -1;
580
581 /// <summary>
582 /// Creates a new CollectionSymbol.
583 /// </summary>
584 /// <param name="collection">The collection for the symbol.</param>
585 public CollectionSymbol(ElementCollection collection)
586 {
587 this.collection = collection;
588 }
589
590 /// <summary>
591 /// Gets the collection for the symbol.
592 /// </summary>
593 public ElementCollection Collection
594 {
595 get { return this.collection; }
596 }
597
598 /// <summary>
599 /// Gets and sets the index of the container in the collection.
600 /// </summary>
601 public int ContainerIndex
602 {
603 get { return this.containerIndex; }
604 set { this.containerIndex = value; }
605 }
606
607 /// <summary>
608 /// Gets and sets the index of the item in the container.
609 /// </summary>
610 public int ItemIndex
611 {
612 get { return this.itemIndex; }
613 set { this.itemIndex = value; }
614 }
615 }
616 }
617 }
618}