using System; using System.CodeDom; using System.Collections.Generic; using System.Linq; using System.Reflection; using Chernobyl.Collections.Generic.Event; using Chernobyl.Event; using Chernobyl.Switch; namespace Chernobyl.Reflection.Template.CodeDom { /// /// An that can take any /// instance and generate CodeDom for it. /// public abstract class CodeDomMember : MemberDecorator { /// /// Initializes a new instance of the class. /// /// The instance to /// generate CodeDom for and provide the /// implementation for this class. protected CodeDomMember(IMember member) : base(false) { DecoratingEventCollection argumentExpressions = new DecoratingEventCollection(new List()); argumentExpressions.ItemsAdded += ArgumentExpressionAdded; argumentExpressions.ItemsRemoved += ArgumentExpressionRemoved; ArgumentExpressions = argumentExpressions; // set and configure the implementer _memberImplementer = member; ConfigureImplementer(); } /// /// The that is the parent to this /// . This will be /// added to the parent 's /// list. If this /// is already a child to another then it will /// be removed from that 's /// list. Setting null on this property /// will cause this to be removed from it's /// parent, if necessary. /// public override IComponent Parent { get { return base.Parent; } set { if(base.Parent != value) { base.Parent = value; // ensure our base class believes this is actually our new parent // before attempting to do anything if (base.Parent == value && base.Parent != null) { CodeDomInstance parentInstance = base.Parent as CodeDomInstance; if (parentInstance != null) { // create and add the declaration of the member's class parentInstance.TypeDeclaration.Members.Add(TypeDeclaration); // add a field and property that will hold an instance of the member's class string fieldAndPropertyBaseName = TypeDeclaration.Name + "_Instance"; CodeMemberField field = new CodeMemberField(TypeDeclaration.Name, "_" + fieldAndPropertyBaseName); CodeFieldReferenceExpression fieldReference = new CodeFieldReferenceExpression(parentInstance.ThisReference, field.Name); parentInstance.TypeDeclaration.Members.Add(field); CodeMemberProperty property = new CodeMemberProperty(); property.Name = fieldAndPropertyBaseName; property.Type = new CodeTypeReference(TypeDeclaration.Name); property.HasGet = true; property.HasSet = true; property.GetStatements.Add(new CodeMethodReturnStatement(fieldReference)); property.SetStatements.Add(new CodeAssignStatement(fieldReference, new CodePropertySetValueReferenceExpression())); parentInstance.TypeDeclaration.Members.Add(property); // create a reference to the member's class instance property CodePropertyReferenceExpression memberClassProperty = new CodePropertyReferenceExpression(parentInstance.ThisReference, property.Name); // create the creation of the member's class in the constructor of the instance's class CodeExpression[] memberClassArguments = new[] { new CodeVariableReferenceExpression("services"), parentInstance.ThisReference }; CodeAssignStatement memberClassPropertyAssignment = new CodeAssignStatement(memberClassProperty, new CodeObjectCreateExpression(TypeDeclaration.Name, memberClassArguments)); parentInstance.ClassConstructor.Statements.Add(memberClassPropertyAssignment); // create the expression that adds the member's class instance to the ComponentChildren // property of the instance's class CodeMethodInvokeExpression memberClassInstanceAddToComponents = new CodeMethodInvokeExpression(parentInstance.ComponentChildrenReference, "Add", memberClassProperty); parentInstance.ClassConstructor.Statements.Add(memberClassInstanceAddToComponents); } } } } } /// /// The declaration of the type that can be used to create and initialize /// this member. /// public CodeTypeDeclaration TypeDeclaration { get { if(_typeDeclaration == null) { _typeDeclaration = new CodeTypeDeclaration(CodeName) { TypeAttributes = TypeAttributes.NotPublic }; // derive from the Member base class and add our constructor _typeDeclaration.BaseTypes.Add(typeof(Member)); _typeDeclaration.Members.Add(ClassConstructor); } return _typeDeclaration; } } /// /// The constructor of the type specified by /// which initializes the /// . /// public CodeConstructor ClassConstructor { get { if(_classConstructor == null) { _classConstructor = new CodeConstructor { Attributes = MemberAttributes.Public }; // create the parameters of the constructor CodeParameterDeclarationExpression firstParameter = new CodeParameterDeclarationExpression(typeof (IEventCollection), "services"); _classConstructor.Parameters.Add(firstParameter); CodeParameterDeclarationExpression secondParameter = new CodeParameterDeclarationExpression(typeof(Instance<>).MakeGenericType(Instance.Type), "instance"); _classConstructor.Parameters.Add(secondParameter); // add a call to the base constructor with the appropriate arguments _classConstructor.BaseConstructorArgs.Add(new CodeVariableReferenceExpression(firstParameter.Name)); _classConstructor.BaseConstructorArgs.Add(new CodePrimitiveExpression(Name)); _classConstructor.BaseConstructorArgs.Add(new CodeVariableReferenceExpression(secondParameter.Name)); } return _classConstructor; } } /// /// The code that generates the field for the initialization /// . This member is used to invoke the /// when the children instances have /// been initialized. /// public CodeMemberField InitializationCounterField { get { if (_initializationCounterField == null) { _initializationCounterField = new CodeMemberField(typeof(CountdownSwitch), "_initializationCounter"); _typeDeclaration.Members.Add(_initializationCounterField); } return _initializationCounterField; } } /// /// The code that generates a reference to the field created by /// . /// public CodeFieldReferenceExpression InitializationCounterFieldReference { get { if(_initializationCounterFieldReference == null) { _initializationCounterFieldReference = new CodeFieldReferenceExpression(ThisReference, InitializationCounterField.Name); // Create the counter field creation statement so that it // will get added to the class constructor statement list. // If we did not do this, the CountdownSwitch field in the // generated code might get used before it was created which // would obviously create a crash. CreateCounterFieldCreationStatement(); } return _initializationCounterFieldReference; } } /// /// The code the creates the . /// public CodeObjectCreateExpression InitializationCounterFieldCreation { get { CreateCounterFieldCreationStatement(); return _initializationCounterFieldCreation; } } /// /// Creates the /// statement and adds it to the /// list of the . /// /// void CreateCounterFieldCreationStatement() { if (_initializationCounterFieldCreation == null) { // create the initialization counter field creation statement _initializationCounterFieldCreation = new CodeObjectCreateExpression(); _initializationCounterFieldCreation.CreateType = new CodeTypeReference(typeof(CountdownSwitch)); _classConstructor.Statements.Add(new CodeAssignStatement(InitializationCounterFieldReference, _initializationCounterFieldCreation)); // assign the initialization method to the initialization // CountdownSwitch's ISwitch.SwitchedOn event CodeAttachEventStatement initializationCounterEventAttach = new CodeAttachEventStatement(InitializationCounterFieldReference, "SwitchedOn", new CodeMethodReferenceExpression(ThisReference, InitializeMethodName)); _classConstructor.Statements.Add(initializationCounterEventAttach); } } /// /// The CodeDom method where the value of the member is set (in the case /// of a field, property, etc) or the method is invoked. By default, this /// is set to the . Note that, when set, /// this property will move the /// from the old to the newly specified /// one. /// public virtual CodeMemberMethod InitializeMethod { get { if(_initializeMethod == null) _initializeMethod = ClassConstructor; return _initializeMethod; } set { if(_initializeMethod != value) { CodeMemberMethod previousMethod = _initializeMethod; _initializeMethod = value; // We don't want to copy over all of the statements from the // constructor since it contains some statements we do not want // in the initialization method. if (previousMethod != null && previousMethod != ClassConstructor) { // move the statements from the previous method to the new one foreach (CodeStatement codeStatement in previousMethod.Statements) _initializeMethod.Statements.Add(codeStatement); } } } } /// /// The reference to the property /// of the /// property. /// /// Thrown if the /// property is not set. public CodeExpression TheGenericInstancePropertyReference { get { if(_theGenericInstancePropertyReference == null) { if(Parent == null) throw new Exception("This property requires that the " + "Parent property be set."); // Create the reference to the "instance" constructor // variable or "IMember.Instance" property depending on the // context. CodeExpression instanceReferenceExpression; if(InitializeMethod == ClassConstructor) { // The method that invokes this member is the constructor // so we can just us the "instance" variable to avoid // casting. instanceReferenceExpression = new CodeVariableReferenceExpression("instance"); } else { // We are not in the constructor so we have to grab the // "Instance" property and cast it as appropriate. CodePropertyReferenceExpression parentInstance = new CodePropertyReferenceExpression(ThisReference, "Instance"); instanceReferenceExpression = new CodeCastExpression(typeof(Instance<>).MakeGenericType(Instance.Type), parentInstance); } _theGenericInstancePropertyReference = new CodePropertyReferenceExpression(instanceReferenceExpression, "TheGenericInstance"); } return _theGenericInstancePropertyReference; } } /// /// A reference to the "this". Used by children to reference the CodeDom /// class generated by this class. /// public CodeExpression ThisReference { get { return _thisReference ?? (_thisReference = new CodeThisReferenceExpression()); } } /// /// A that references the /// property of the CodeDom /// class generated by this class. /// public CodeExpression ComponentChildrenReference { get { if(_componentChildrenReference == null) _componentChildrenReference = new CodePropertyReferenceExpression(ThisReference, "ComponentChildren"); return _componentChildrenReference; } } /// /// A reference to an by the name of "services". /// public CodeExpression ServicesReference { get { return _servicesReference ?? (_servicesReference = new CodeVariableReferenceExpression("services")); } } /// /// An of s that /// represent the arguments passed to the member. /// public ICollection ArgumentExpressions { get; private set; } /// /// An event handler that is invoked right after an argument /// s are added to the /// list. /// /// The instance that generated the event. /// The instance /// containing the event data. protected virtual void ArgumentExpressionAdded(object sender, ItemsEventArgs e) { ResetInitializationCounterAmount(); } /// /// An event handler that is invoked right after an argument /// s are removed from the /// list. /// /// The instance that generated the event. /// The instance /// containing the event data. protected virtual void ArgumentExpressionRemoved(object sender, ItemsEventArgs e) { ResetInitializationCounterAmount(); } /// /// The instance that implements the /// functionality of this class. /// protected override IMember MemberImplementer { get { return _memberImplementer; } } /// /// Resets the value of the with the latest /// from . /// void ResetInitializationCounterAmount() { // We need to only count instances that aren't using CodeSnippetExpression. // If they are, then they are primitive instances (like "Hello World!" or 3.14) // and do not generate an entire class for themselves so we can't // rely on their IInstance.BecameReady event to be fired. int count = ArgumentExpressions.Where(codeExpression => !(codeExpression is CodeSnippetExpression)).Count(); if (count != 0) { CodePrimitiveExpression parameter = new CodePrimitiveExpression(count); if (InitializationCounterFieldCreation.Parameters.Count == 0) InitializationCounterFieldCreation.Parameters.Add(parameter); else InitializationCounterFieldCreation.Parameters[0] = parameter; // Create the initialization method. if (_initializeMethod == null || _initializeMethod.Name != InitializeMethodName) { InitializeMethod = new CodeMemberMethod(); InitializeMethod.Name = InitializeMethodName; InitializeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "sender")); InitializeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(EventArgs), "e")); _typeDeclaration.Members.Add(InitializeMethod); } } } /// /// The backing field to . /// CodeTypeDeclaration _typeDeclaration; /// /// The backing field to . /// CodeConstructor _classConstructor; /// /// The backing field to . /// CodeMemberField _initializationCounterField; /// /// The backing field to . /// CodeFieldReferenceExpression _initializationCounterFieldReference; /// /// The backing field to . /// CodeObjectCreateExpression _initializationCounterFieldCreation; /// /// The backing field to . /// CodeMemberMethod _initializeMethod; /// /// The name of the if it is not the /// constructor. /// const string InitializeMethodName = "InitializeMethod"; /// /// The backing field to . /// CodePropertyReferenceExpression _theGenericInstancePropertyReference; /// /// The backing field to . /// IMember _memberImplementer; /// /// The backing field to . /// CodeExpression _servicesReference; /// /// The backing field to . /// CodeExpression _thisReference; /// /// The backing field to . /// CodeExpression _componentChildrenReference; } }