summaryrefslogtreecommitdiff
path: root/src/tools/heat/Serialize/ElementCollection.cs
diff options
context:
space:
mode:
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}