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