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