using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using Chernobyl.Reflection.Template.Xml; namespace Chernobyl.Reflection.Template { /// /// An and that /// parses a string for type information and allows iteration over the /// s discovered during parsing. Note that this class /// parses as it iterates. /// public class TypesParser : IEnumerable, IEnumerator { /// /// Initializes a new instance of the class. /// /// The text to be parsed. /// The /// that will be used to look up the /// namespace and assembly. The passed to the /// method will be the namespace prefix or an empty string if the /// namespace/assembly of the current scope is requested. The /// returned from the method will be /// the namespace and assembly name for the prefix passed to the /// method. /// public TypesParser(string text, Func namespaceAssemblyLookup) { Text = text; NamespaceAssemblyLookup = namespaceAssemblyLookup; Reset(); } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through /// the collection. /// public IEnumerator GetEnumerator() { Reset(); return this; } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate /// through the collection. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Gets the element in the collection at the current position of the /// enumerator. /// /// /// The element in the collection at the current position of the /// enumerator. /// public Type Current { get { return _currentTypeParser.TheType; } } /// /// Performs application-defined tasks associated with freeing, /// releasing, or resetting unmanaged resources. /// public void Dispose() { } /// /// Gets the element in the collection at the current position of the /// enumerator. /// /// /// The element in the collection at the current position of the /// enumerator. /// object IEnumerator.Current { get { return Current; } } /// /// Advances the enumerator to the next element of the collection. /// /// /// true if the enumerator was successfully advanced to the next /// element; false if the enumerator has passed the end of the collection. /// /// The collection was /// modified after the enumerator was created. public bool MoveNext() { bool result = false; // Create a lambda that we will use to create the TypeParser. This // reduces code duplication in the code below. Func createParser = text => new TypeParser(text, NamespaceAssemblyLookup); if (_currentTypeParser != null) { // Since the _currentTypeParser is not null, we have already // read in at least one type, so we need to move past the next // type separator, if it exists. If we hit a generic parameter // end separator or neither, we are at the end of the string and // done parsing. int position = Text.IndexOfAny(_genericParametersEndOrTypesSeparators, Position); if (position != -1) { if (Text[position] == Separators.BetweenTypes) { // We want everything after the comma so add one to the position. _currentTypeParser = createParser(Text.Substring(position + 1)); Position = Position + _currentTypeParser.Position + 1; result = true; } else { // We've run into a generic parameter end separator which // means we are done reading this set of types; add one // to allow others to move past the bracket. Position = position + 1; } } } else { // We have not read anything from the string yet so we don't // have to worry about the end of the string. _currentTypeParser = createParser(Text); Position = _currentTypeParser.Position; result = true; } return result; } /// /// Sets the enumerator to its initial position, which is before the /// first element in the collection. /// /// /// The collection was modified after the enumerator was created. /// public void Reset() { Position = 0; _currentTypeParser = null; } /// /// Parses over text to grab information about a particular type. /// public class TypeParser { /// /// Initializes a new instance of the class. /// /// The text to be parsed. /// The /// that will be used to look up the /// namespace and assembly. The passed to the /// method will be the namespace prefix or an empty string if the /// namespace/assembly of the current scope is requested. The /// returned from the method will be /// the namespace and assembly name for the prefix passed to the /// method. public TypeParser(string text, Func namespaceAssemblyLookup) { // In case there are two types in the text string, we'll locate // the start of the second one and make that the 'end' of the // text that this constructor needs to read to. Otherwise, we'll // just use the index right after the end. int endIndex = text.IndexOfAny(Separators.All); if (endIndex == -1) endIndex = text.Length; // Grab the namespace and assembly prefix from the text. string namespaceAssemblyPrefix = string.Empty; int colonIndex = -1; if (text.Length != endIndex && text[endIndex] == Separators.NamespaceAssemblyTypeName) { colonIndex = endIndex; namespaceAssemblyPrefix = text.Substring(0, colonIndex).Trim(); // Get the index of the next separator or the end of the // text if the separator doesn't exist. endIndex = text.IndexOfAny(Separators.GenericParametersAndTypes, endIndex); if (endIndex == -1) endIndex = text.Length; } NamespaceAssembly namespaceAssembly = namespaceAssemblyLookup(namespaceAssemblyPrefix); // Grab the name of the type and the generic parameters, if // they exist. Type[] genericParameters = null; int typeNameEndingCharacterIndex; if (text.Length != endIndex && text[endIndex] == Separators.GenericParametersStart) { typeNameEndingCharacterIndex = endIndex; // This type is generic, read in the generic parameters. To // do this, we'll move the endIndex one place after the // bracket so that we start reading everything after it. endIndex += 1; TypesParser genericParametersParser = new TypesParser(text.Substring(endIndex), namespaceAssemblyLookup); genericParameters = genericParametersParser.ToArray(); Position = endIndex + genericParametersParser.Position; } else { // There are no generic parameters so just go to the // next comma (in the case where the type is in type list) // or to the end of the string. Position = typeNameEndingCharacterIndex = endIndex; } // Now get the name of the type. It should be located one place // after the colon at the end of the namespace/assembly prefix. int typeNameStartingCharacterIndex = colonIndex == -1 ? 0 : colonIndex + 1; int countFromFirstToLastCharacterIndex = typeNameEndingCharacterIndex - typeNameStartingCharacterIndex; string typeName = text.Substring(colonIndex + 1, countFromFirstToLastCharacterIndex).Trim(); // find the assembly Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); IEnumerable locatedAssemblies = assemblies.Where(asm => AssemblyName.ReferenceMatchesDefinition(asm.GetName(), namespaceAssembly.Assembly)); // Make sure at least one assembly was found. if (locatedAssemblies.Any() == false) throw new TypeLoadException("Failed to load the type \"" + typeName + "\". The assembly \"" + namespaceAssembly.Assembly + "\" could not be found. "); using (IEnumerator locatedAssembliesEnumerator = locatedAssemblies.GetEnumerator()) { // build the full type name string fullTypeName = namespaceAssembly.Namespace + '.' + typeName; // include the generic parameter 'tag' at the end, if necessary if (genericParameters != null) fullTypeName = fullTypeName + '`' + genericParameters.Length; // locate the type in the assemblies using the full type name while (locatedAssembliesEnumerator.MoveNext() == true && TheType == null) TheType = locatedAssembliesEnumerator.Current.GetType(fullTypeName); // Ensure we found a type in one of the assemblies. if (TheType == null) { // The type was not found, generate a newline separated // list of the assemblies we searched. This information // will be placed within the exception to allow the user // to debug their application easier. string assemblyList = locatedAssemblies.Select(asm => asm.ToString()).Aggregate( (workingAssembly, next) => workingAssembly.ToString() + Environment.NewLine + next.ToString()); throw new TypeLoadException("The type \"" + fullTypeName + "\" could not be found. The following assemblies " + "were searched: " + Environment.NewLine + assemblyList); } } // add on the generic parameters, if necessary if (genericParameters != null) TheType = TheType.MakeGenericType(genericParameters); } /// /// The type that was parsed or null if the type could not be /// located. /// public Type TheType { get; private set; } /// /// The current zero based index position within the text being worked on. /// public int Position { get; set; } } /// /// The text that is being parsed. /// public string Text { get; private set; } /// /// The current zero based index position within the text being worked on. /// public int Position { get; set; } /// /// The that will be used to look up the /// namespace and assembly. The passed to the method /// will be the namespace prefix or an empty string if the /// namespace/assembly of the current scope is requested. The /// returned from the method will be /// the namespace and assembly name for the prefix passed to the method. /// public Func NamespaceAssemblyLookup; /// /// The that was parsed and whose /// represents the /// or null if the method /// has not been called yet. /// TypeParser _currentTypeParser; /// /// Holds the separators used for separating out data in a type string. /// public static class Separators { /// /// The separator that is placed between the namespace assembly /// prefix and the type name. /// public const char NamespaceAssemblyTypeName = ':'; /// /// The separator that is placed between types in a type list. /// public const char BetweenTypes = ','; /// /// The separator that is placed between the type name and the /// generic parameter list of a generic type. /// public const char GenericParametersStart = '{'; /// /// The separator that is placed at the end of a generic parameter /// list. /// public const char GenericParametersEnd = '}'; /// /// All of the separators that deal with starting and ending of a /// generic parameter list and the separation of types in a type /// list. /// public static readonly char[] GenericParametersAndTypes = new[] { GenericParametersStart, BetweenTypes, GenericParametersEnd }; /// /// All of the separators for a type string. /// public static readonly char[] All = new[] { NamespaceAssemblyTypeName, GenericParametersStart, BetweenTypes, GenericParametersEnd }; } /// /// The characters that mark the end of generic parameters and separate /// types. /// readonly char[] _genericParametersEndOrTypesSeparators = new[] { Separators.GenericParametersEnd, Separators.BetweenTypes }; } }