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