From 6a24996a2e831cfe402398af65b31fb1ecd575a9 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 22 Apr 2021 16:36:39 -0700 Subject: Move WixBuildTools into internal --- .../WixBuildTools.XsdGen/ElementCollection.cs | 642 +++++++++++++++++++++ 1 file changed, 642 insertions(+) create mode 100644 src/internal/WixBuildTools.XsdGen/ElementCollection.cs (limited to 'src/internal/WixBuildTools.XsdGen/ElementCollection.cs') 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 @@ +// 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. + +namespace WixToolset.Serialize +{ + using System; + using System.Collections; + using System.Globalization; + + /// + /// Collection used in the CodeDOM for the children of a given element. Provides type-checking + /// on the allowed children to ensure that only allowed types are added. + /// + public class ElementCollection : ICollection, IEnumerable + { + private CollectionType collectionType; + private int minimum = 1; + private int maximum = 1; + private int totalContainedItems; + private int containersUsed; + private ArrayList items; + + /// + /// Creates a new element collection. + /// + /// Type of the collection to create. + public ElementCollection(CollectionType collectionType) + { + this.collectionType = collectionType; + this.items = new ArrayList(); + } + + /// + /// Creates a new element collection. + /// + /// Type of the collection to create. + /// When used with a type 'Choice', specifies a minimum number of allowed children. + /// When used with a type 'Choice', specifies a maximum number of allowed children. + public ElementCollection(CollectionType collectionType, int minimum, int maximum) : this(collectionType) + { + this.minimum = minimum; + this.maximum = maximum; + } + + /// + /// Enum representing types of XML collections. + /// + public enum CollectionType + { + /// + /// A choice type, corresponding to the XSD choice element. + /// + Choice, + + /// + /// A sequence type, corresponding to the XSD sequence element. + /// + Sequence + } + + /// + /// Gets the type of collection. + /// + /// The type of collection. + public CollectionType Type + { + get { return this.collectionType; } + } + + /// + /// Gets the count of child elements in this collection (counts ISchemaElements, not nested collections). + /// + /// The count of child elements in this collection (counts ISchemaElements, not nested collections). + public int Count + { + get { return this.totalContainedItems; } + } + + /// + /// Gets the flag specifying whether this collection is synchronized. Always returns false. + /// + /// The flag specifying whether this collection is synchronized. Always returns false. + public bool IsSynchronized + { + get { return false; } + } + + /// + /// Gets an object external callers can synchronize on. + /// + /// An object external callers can synchronize on. + public object SyncRoot + { + get { return this; } + } + + /// + /// Adds a child element to this collection. + /// + /// The element to add. + /// Thrown if the child is not of an allowed type. + public void AddElement(ISchemaElement element) + { + foreach (object obj in this.items) + { + bool containerUsed; + + CollectionItem collectionItem = obj as CollectionItem; + if (collectionItem != null) + { + containerUsed = collectionItem.Elements.Count != 0; + if (collectionItem.ElementType.IsAssignableFrom(element.GetType())) + { + collectionItem.AddElement(element); + + if (!containerUsed) + { + this.containersUsed++; + } + + this.totalContainedItems++; + return; + } + + continue; + } + + ElementCollection collection = obj as ElementCollection; + if (collection != null) + { + containerUsed = collection.Count != 0; + + try + { + collection.AddElement(element); + + if (!containerUsed) + { + this.containersUsed++; + } + + this.totalContainedItems++; + return; + } + catch (ArgumentException) + { + // Eat the exception and keep looking. We'll throw our own if we can't find its home. + } + + continue; + } + } + + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + "Element of type {0} is not valid for this collection.", + element.GetType().Name)); + } + + /// + /// Removes a child element from this collection. + /// + /// The element to remove. + /// Thrown if the element is not of an allowed type. + public void RemoveElement(ISchemaElement element) + { + foreach (object obj in this.items) + { + CollectionItem collectionItem = obj as CollectionItem; + if (collectionItem != null) + { + if (collectionItem.ElementType.IsAssignableFrom(element.GetType())) + { + if (collectionItem.Elements.Count == 0) + { + return; + } + + collectionItem.RemoveElement(element); + + if (collectionItem.Elements.Count == 0) + { + this.containersUsed--; + } + + this.totalContainedItems--; + return; + } + + continue; + } + + ElementCollection collection = obj as ElementCollection; + if (collection != null) + { + if (collection.Count == 0) + { + continue; + } + + try + { + collection.RemoveElement(element); + + if (collection.Count == 0) + { + this.containersUsed--; + } + + this.totalContainedItems--; + return; + } + catch (ArgumentException) + { + // Eat the exception and keep looking. We'll throw our own if we can't find its home. + } + + continue; + } + } + + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + "Element of type {0} is not valid for this collection.", + element.GetType().Name)); + } + + /// + /// Copies this collection to an array. + /// + /// Array to copy to. + /// Offset into the array. + public void CopyTo(Array array, int index) + { + int item = 0; + foreach (ISchemaElement element in this) + { + array.SetValue(element, (long)(item + index)); + item++; + } + } + + /// + /// Creates an enumerator for walking the elements in this collection. + /// + /// A newly created enumerator. + public IEnumerator GetEnumerator() + { + return new ElementCollectionEnumerator(this); + } + + /// + /// Gets an enumerable collection of children of a given type. + /// + /// Type of children to get. + /// A collection of children. + /// Thrown if the type isn't a valid child type. + public IEnumerable Filter(Type childType) + { + foreach (object container in this.items) + { + CollectionItem collectionItem = container as CollectionItem; + if (collectionItem != null) + { + if (collectionItem.ElementType.IsAssignableFrom(childType)) + { + return collectionItem.Elements; + } + + continue; + } + + ElementCollection elementCollection = container as ElementCollection; + if (elementCollection != null) + { + IEnumerable nestedFilter = elementCollection.Filter(childType); + if (nestedFilter != null) + { + return nestedFilter; + } + + continue; + } + } + + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + "Type {0} is not valid for this collection.", + childType.Name)); + } + + /// + /// Adds a type to this collection. + /// + /// CollectionItem representing the type to add. + public void AddItem(CollectionItem collectionItem) + { + this.items.Add(collectionItem); + } + + /// + /// Adds a nested collection to this collection. + /// + /// ElementCollection to add. + public void AddCollection(ElementCollection collection) + { + this.items.Add(collection); + } + + /// + /// Class used to represent a given type in the child collection of an element. Abstract, + /// has subclasses for choice and sequence (which can do cardinality checks). + /// + public abstract class CollectionItem + { + private Type elementType; + private ArrayList elements; + + /// + /// Creates a new CollectionItem for the given element type. + /// + /// Type of the element for this collection item. + public CollectionItem(Type elementType) + { + this.elementType = elementType; + this.elements = new ArrayList(); + } + + /// + /// Gets the type of this collection's items. + /// + public Type ElementType + { + get { return this.elementType; } + } + + /// + /// Gets the elements of this collection. + /// + public ArrayList Elements + { + get { return this.elements; } + } + + /// + /// Adds an element to this collection. Must be of an assignable type to the collection's + /// type. + /// + /// The element to add. + /// Thrown if the type isn't assignable to the collection's type. + public void AddElement(ISchemaElement element) + { + if (!this.elementType.IsAssignableFrom(element.GetType())) + { + throw new ArgumentException( + String.Format( + CultureInfo.InvariantCulture, + "Element must be a subclass of {0}, but was of type {1}.", + this.elementType.Name, + element.GetType().Name), + "element"); + } + + this.elements.Add(element); + } + + /// + /// Removes an element from this collection. + /// + /// The element to remove. + /// Thrown if the element's type isn't assignable to the collection's type. + public void RemoveElement(ISchemaElement element) + { + if (!this.elementType.IsAssignableFrom(element.GetType())) + { + throw new ArgumentException( + String.Format( + CultureInfo.InvariantCulture, + "Element must be a subclass of {0}, but was of type {1}.", + this.elementType.Name, + element.GetType().Name), + "element"); + } + + this.elements.Remove(element); + } + } + + /// + /// Class representing a choice item. Doesn't do cardinality checks. + /// + public class ChoiceItem : CollectionItem + { + /// + /// Creates a new choice item. + /// + /// Type of the created item. + public ChoiceItem(Type elementType) : base(elementType) + { + } + } + + /// + /// Class representing a sequence item. Can do cardinality checks, if required. + /// + public class SequenceItem : CollectionItem + { + private int minimum = 1; + private int maximum = 1; + + /// + /// Creates a new sequence item. + /// + /// Type of the created item. + public SequenceItem(Type elementType) : base(elementType) + { + } + + /// + /// Creates a new sequence item with the specified minimum and maximum. + /// + /// Type of the created item. + /// Minimum number of elements. + /// Maximum number of elements. + public SequenceItem(Type elementType, int minimum, int maximum) : base(elementType) + { + this.minimum = minimum; + this.maximum = maximum; + } + } + + /// + /// Enumerator for the ElementCollection. + /// + private class ElementCollectionEnumerator : IEnumerator + { + private ElementCollection collection; + private Stack collectionStack; + + /// + /// Creates a new ElementCollectionEnumerator. + /// + /// The collection to create an enumerator for. + public ElementCollectionEnumerator(ElementCollection collection) + { + this.collection = collection; + } + + /// + /// Gets the current object from the enumerator. + /// + public object Current + { + get + { + if (this.collectionStack != null && this.collectionStack.Count > 0) + { + CollectionSymbol symbol = (CollectionSymbol)this.collectionStack.Peek(); + object container = symbol.Collection.items[symbol.ContainerIndex]; + + CollectionItem collectionItem = container as CollectionItem; + if (collectionItem != null) + { + return collectionItem.Elements[symbol.ItemIndex]; + } + + throw new InvalidOperationException(String.Format( + CultureInfo.InvariantCulture, + "Element of type {0} found in enumerator. Must be ChoiceItem or SequenceItem.", + container.GetType().Name)); + } + + return null; + } + } + + /// + /// Resets the enumerator to the beginning. + /// + public void Reset() + { + if (this.collectionStack != null) + { + this.collectionStack.Clear(); + this.collectionStack = null; + } + } + + /// + /// Moves the enumerator to the next item. + /// + /// True if there is a next item, false otherwise. + public bool MoveNext() + { + if (this.collectionStack == null) + { + if (this.collection.Count == 0) + { + return false; + } + + this.collectionStack = new Stack(); + this.collectionStack.Push(new CollectionSymbol(this.collection)); + } + + CollectionSymbol symbol = (CollectionSymbol)this.collectionStack.Peek(); + + if (this.FindNext(symbol)) + { + return true; + } + + this.collectionStack.Pop(); + if (this.collectionStack.Count == 0) + { + return false; + } + + return this.MoveNext(); + } + + /// + /// Pushes a collection onto the stack. + /// + /// The collection to push. + private void PushCollection(ElementCollection collection) + { + if (collection.Count <= 0) + { + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + "Collection has {0} elements. Must have at least one.", + collection.Count)); + } + + CollectionSymbol symbol = new CollectionSymbol(collection); + this.collectionStack.Push(symbol); + this.FindNext(symbol); + } + + /// + /// Finds the next item from a given symbol. + /// + /// The symbol to start looking from. + /// True if a next element is found, false otherwise. + private bool FindNext(CollectionSymbol symbol) + { + object container = symbol.Collection.items[symbol.ContainerIndex]; + + CollectionItem collectionItem = container as CollectionItem; + if (collectionItem != null) + { + if (symbol.ItemIndex + 1 < collectionItem.Elements.Count) + { + symbol.ItemIndex++; + return true; + } + } + + ElementCollection elementCollection = container as ElementCollection; + if (elementCollection != null && elementCollection.Count > 0 && symbol.ItemIndex == -1) + { + symbol.ItemIndex++; + this.PushCollection(elementCollection); + return true; + } + + symbol.ItemIndex = 0; + + for (int i = symbol.ContainerIndex + 1; i < symbol.Collection.items.Count; ++i) + { + object nestedContainer = symbol.Collection.items[i]; + + CollectionItem nestedCollectionItem = nestedContainer as CollectionItem; + if (nestedCollectionItem != null) + { + if (nestedCollectionItem.Elements.Count > 0) + { + symbol.ContainerIndex = i; + return true; + } + } + + ElementCollection nestedElementCollection = nestedContainer as ElementCollection; + if (nestedElementCollection != null && nestedElementCollection.Count > 0) + { + symbol.ContainerIndex = i; + this.PushCollection(nestedElementCollection); + return true; + } + } + + return false; + } + + /// + /// Class representing a single point in the collection. Consists of an ElementCollection, + /// a container index, and an index into the container. + /// + private class CollectionSymbol + { + private ElementCollection collection; + private int containerIndex; + private int itemIndex = -1; + + /// + /// Creates a new CollectionSymbol. + /// + /// The collection for the symbol. + public CollectionSymbol(ElementCollection collection) + { + this.collection = collection; + } + + /// + /// Gets the collection for the symbol. + /// + public ElementCollection Collection + { + get { return this.collection; } + } + + /// + /// Gets and sets the index of the container in the collection. + /// + public int ContainerIndex + { + get { return this.containerIndex; } + set { this.containerIndex = value; } + } + + /// + /// Gets and sets the index of the item in the container. + /// + public int ItemIndex + { + get { return this.itemIndex; } + set { this.itemIndex = value; } + } + } + } + } +} -- cgit v1.2.3-55-g6feb