using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Chernobyl.Collections.Generic.Event;
using Chernobyl.Reflection.Template.CodeDom;
namespace Chernobyl.Reflection.Template
{
///
/// A template for some method, property, field, or constructor. This class
/// holds information about a member so that this method can be returned to
/// some specified state.
///
public interface IMember : IComponent
{
///
/// The this belongs to.
///
IInstance Instance { get; }
///
/// Information about this member.
///
MemberInfo Info { get; }
}
///
/// A default implementation of an IMember. This class can be used to hold
/// information about a member or can be used to make creation of derived
/// IMember instances easier.
///
public class Member : Component, IMember
{
///
/// Constructor.
///
/// The services instance to use when injecting
/// or receiving dependencies from created instances.
/// The name of the member.
/// The this
/// belongs to.
public Member(IEventCollection services, string name, IInstance instance)
: base(services, string.Empty, name)
{
Instance = instance;
}
///
/// Information about this member.
///
public MemberInfo Info
{
get
{
if(_info == null)
{
MemberInfo[] members = Instance.Type.GetMember(Name);
MemberInfo enumerableMember = null;
if(members.Length > 1)
{
// there are several members that are likely candidates
// so we'll need to find the best one based on arguments
// grab the types of the arguments
IEnumerable arguments = ComponentChildren.OfType();
IEnumerable argumentTypes = from arg in arguments
select arg.Type;
IEnumerable methodBases = members.OfType();
int argumentCount = argumentTypes.Count();
// if any argument type is null, then we will just grab
// the closest method we find since we can no longer be
// sure of the member we choose at that point. An
// argument type will be null if one or more of the
// children are a StringRepresentedInstance, for example.
if(argumentTypes.Any(type => type == null) == true)
_info = methodBases.First(methodBase => methodBase.GetParameters().Length == argumentCount);
else
{
// rule out members based on number of arguments. If
// we find a method that takes an IEnumerable, we'll
// include it as well so that we can make it easier
// to operate on methods that several instances.
MethodBase[] methods = (from method in methodBases
let parameters = method.GetParameters()
where parameters.Length == argumentCount ||
(parameters.Length == 1 && typeof(IEnumerable).IsAssignableFrom(parameters[0].ParameterType))
select method).ToArray();
// TODO: change the exception type that is thrown here.
if(methods.Length == 0)
throw new Exception("The member \"" + Name +
"\" on the the instance \"" + Instance.Name +
"\" does not have a signature that takes \"" +
argumentCount + "\" arguments you have specified.");
// now we need to compare the types of the parameters
// in each method with the types of our arguments;
for (int i = 0; i < methods.Length && _info == null; i++)
{
MethodBase method = methods[i];
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == argumentCount)
{
IEnumerable memberArgumentTypes = from parameterInfo in parameters
select parameterInfo.ParameterType;
// Check the types of the our instances against the types
// accepted by the member to see if this is the member the
// user is referring to.
_info = method;
using (IEnumerator argumentTypesEnumerator = argumentTypes.GetEnumerator())
using (IEnumerator memberArgumentTypesEnumerator = memberArgumentTypes.GetEnumerator())
{
while (_info != null &&
argumentTypesEnumerator.MoveNext() == true &&
memberArgumentTypesEnumerator.MoveNext() == true)
{
if (memberArgumentTypesEnumerator.Current.IsAssignableFrom(argumentTypesEnumerator.Current) == false)
_info = null;
}
}
}
// For faster processing in the event that this is
// a method that takes an IEnumerable, we will check
// for it here and use the value later on.
if (enumerableMember == null &&
parameters.Length == 1 &&
typeof(IEnumerable).IsAssignableFrom(parameters[0].ParameterType) == true)
enumerableMember = method;
}
// if the info is still null then we did not find the
// proper method/constructor. The user may be trying
// to create an IEnumerable with items in, in which case
// we will create, if applicable, and fill it with the
// necessary instances
if (_info == null && enumerableMember != null)
{
_info = enumerableMember;
Type argumentCommonBaseType = argumentTypes.GetCommonBaseType();
// Create an array (arrays derive from IEnumerable)
// and a constructor for that array. The constructor
// will get the instances we were given as arguments
IInstance arrayInstance = new CodeDomInstance(new Instance(Services, string.Empty, argumentCommonBaseType.MakeArrayType()));
IMember arrayInstanceConstructor = new CodeDomArrayConstructor(new Member(Services, ".ctor", arrayInstance));
// Note that we make the argument list an array using
// ToArray() because the arguments list would get
// modified as we move arguments to the constructor's
// ComponentChildren list, this would generate an
// exception as we try to iterate over that
// changing IEnumerable
foreach (IComponent component in arguments.Cast().ToArray())
arrayInstanceConstructor.ComponentChildren.Add(component);
arrayInstance.ComponentChildren.Add(arrayInstanceConstructor);
// remove the instances that we gave to the array
// constructor from this member
foreach (IInstance argument in arguments)
ComponentChildren.Remove(argument);
ComponentChildren.Add(arrayInstance);
}
}
}
else if(members.Length == 1)
_info = members.First();
}
return _info;
}
}
///
/// The this belongs to.
///
public IInstance Instance { get; protected set; }
///
/// The name of this component which can be used to generate code for
/// this component.
///
public override string CodeName
{
get
{
// Generate the code name if necessary. If this is a constructor
// it will contain a dot ('.') which we need to remove.
if (string.IsNullOrEmpty(_CodeName) == true)
_CodeName = Name.Replace(".", string.Empty) + "_" + GetHashCode();
return _CodeName;
}
}
///
/// The backing property to .
///
protected string _CodeName { get; set; }
///
/// The backing field to .
///
MemberInfo _info;
}
}