// 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.Harvesters.Serialize { using System; using System.Collections; using System.Globalization; using WixToolset.Harvesters.Extensibility.Serialize; /// /// 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 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(); } /// /// 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, WixHarvesterStrings.EXP_ElementOfTypeIsNotValidForThisCollection, 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, WixHarvesterStrings.EXP_ElementOfTypeIsNotValidForThisCollection, 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, WixHarvesterStrings.EXP_TypeIsNotValidForThisCollection, 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. protected CollectionItem(Type elementType) { this.elementType = elementType; this.elements = new ArrayList(); } /// /// Gets the type of this collection's items. /// /// The type of this collection's items. public Type ElementType { get { return this.elementType; } } /// /// Gets the elements of this collection. /// /// 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, WixHarvesterStrings.EXP_ElementIsSubclassOfDifferentType, 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, WixHarvesterStrings.EXP_ElementIsSubclassOfDifferentType, 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 { /// /// Creates a new sequence item. /// /// Type of the created item. public SequenceItem(Type elementType) : base(elementType) { } } /// /// 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, WixHarvesterStrings.EXP_ElementMustBeChoiceItemOrSequenceItem, 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 elementCollection) { if (elementCollection.Count <= 0) { throw new ArgumentException(String.Format( CultureInfo.InvariantCulture, WixHarvesterStrings.EXP_CollectionMustHaveAtLeastOneElement, elementCollection.Count)); } CollectionSymbol symbol = new CollectionSymbol(elementCollection); 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; } } } } } }