// 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; }
}
}
}
}
}