using System; using System.CodeDom; using System.IO; using System.Linq; using System.Reflection; using Chernobyl.Collections.Generic.Event; using Chernobyl.Dependency; using Chernobyl.IO; using Chernobyl.Reflection.Resources; using Chernobyl.Reflection.Template.CodeDom; using Chernobyl.Reflection.Template.Xml; using Chernobyl.Resources; namespace Chernobyl.Reflection.Template { /// /// An that becomes an instance that is located in /// another location under the name specified in the constructor. This /// will generate CodeDom and add it to the parent /// if the parent is a . /// public class LinkedInstance : CodeDomInstance { /// /// Initializes a new instance of the class. /// /// The instance that gives out services for use /// by this type and takes services from this type for use by other systems. /// The instance /// that contains the information about the XLink pointer. /// The to /// generate CodeDom for and provide the /// implementation for this class. public LinkedInstance(IEventCollection services, XmlInstance.XlinkHref xlink, IInstance decoratedInstance) : base(decoratedInstance, false) { _services = services; Path = xlink.Path; LinkedName = xlink.Name; LinkedMember = xlink.Member; services.Inject(this); ConfigureImplementer(); } /// /// The location where the instance resides. /// public string Path { get; private set; } /// /// The constructor of the type specified by /// which initializes the /// . /// public override CodeConstructor ClassConstructor { get { if (_objectResourceProcessorDeclaration == null) { // Create the code that grabs the // ObjectContentResourceProcessor from the services instance. string objectResourceProcessorName = typeof(IResourceProcessor).ToString().Replace("`1", string.Empty).Replace('[', '<').Replace(']', '>'); CodeMethodInvokeExpression servicesInvoke = new CodeMethodInvokeExpression(ServicesReference, "GetService", new CodeTypeOfExpression(objectResourceProcessorName)); CodeVariableReferenceExpression objectResourceProcessorReference = new CodeVariableReferenceExpression("objectResourceProcessor"); _objectResourceProcessorDeclaration = new CodeVariableDeclarationStatement(typeof(ObjectContentResourceProcessor), objectResourceProcessorReference.VariableName, new CodeCastExpression(typeof(ObjectContentResourceProcessor), servicesInvoke)); base.ClassConstructor.Statements.Add(_objectResourceProcessorDeclaration); // Create the try/finally code that will surrounding the creation of the stream and // the loading of the linked instance. // This code creates: Stream fallFileStream = null; CodePrimitiveExpression nullExpression = new CodePrimitiveExpression(null); CodeVariableDeclarationStatement fallFileStreamDeclaration = new CodeVariableDeclarationStatement(typeof(Stream), "fallFileStream", nullExpression); base.ClassConstructor.Statements.Add(fallFileStreamDeclaration); // This code creates the try block CodeTryCatchFinallyStatement linkedInstanceLoadingTryStatement = new CodeTryCatchFinallyStatement(); base.ClassConstructor.Statements.Add(linkedInstanceLoadingTryStatement); // This code creates: fallFileStream = new FileSearchStream(Services, "Path/To/Linked/FalloutFile.fall", FileMode.Open); CodeVariableReferenceExpression fallFileStreamReference = new CodeVariableReferenceExpression(fallFileStreamDeclaration.Name); CodeObjectCreateExpression fallFileStreamCreation = new CodeObjectCreateExpression(typeof(FileSearchStream), new[] { ServicesReference, new CodePrimitiveExpression(Path), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(typeof(FileMode)), FileMode.Open.ToString()) }); CodeAssignStatement codeAssignStatement = new CodeAssignStatement(fallFileStreamReference, fallFileStreamCreation); linkedInstanceLoadingTryStatement.TryStatements.Add(codeAssignStatement); // This code creates: // finally // { // if (fallFileStream != null) // fallFileStream.Dispose(); // }; CodeMethodInvokeExpression disposeInvocation = new CodeMethodInvokeExpression(fallFileStreamReference, "Dispose"); CodeBinaryOperatorExpression fallFileStreamIsNullCheck = new CodeBinaryOperatorExpression(fallFileStreamReference, CodeBinaryOperatorType.IdentityInequality, nullExpression); CodeConditionStatement conditionalStatement = new CodeConditionStatement(fallFileStreamIsNullCheck, new CodeExpressionStatement(disposeInvocation)); linkedInstanceLoadingTryStatement.FinallyStatements.Add(conditionalStatement); // Create the code that will grab the referenced instance from the ObjectContentResourceProcessor // and add it to the try statement list. Type resourceProcessorCallbackType = typeof(ResourceProcessCallback<>).MakeGenericType(Linked.Type); CodeExpression[] objectProcessorFromInvokeParams = new CodeExpression[] { // This code creates: this.SetMemberResourceProcessCallback new CodeDelegateCreateExpression(new CodeTypeReference(resourceProcessorCallbackType), ThisReference, SetMemberResourceProcessCallback.Name), // This code creates: fallFileStreamReference fallFileStreamReference }; CodeMethodInvokeExpression objectProcessorFromMethodInvoke = new CodeMethodInvokeExpression( new CodeMethodReferenceExpression(objectResourceProcessorReference, "From", new[] { new CodeTypeReference(Linked.Type)}), objectProcessorFromInvokeParams); // This code creates: new ResourceName("NameOfInstance") // this code is only created, however, if the name of the // instance was actually specified. In the case where a name // is not specified, the first instance in the file or null // is returned. if(LinkedName.Length != 0) objectProcessorFromMethodInvoke.Parameters.Add(new CodeObjectCreateExpression(typeof(ResourceName), new CodePrimitiveExpression(LinkedName))); CodeStatement objectProcessorFromInvoke = new CodeExpressionStatement(objectProcessorFromMethodInvoke); linkedInstanceLoadingTryStatement.TryStatements.Add(objectProcessorFromInvoke); // if a member on the referenced instance is getting accessed, we need to make sure to call it CodeExpression resultResourcePropertyReference = new CodePropertyReferenceExpression(new CodeArgumentReferenceExpression("result"), "Resource"); CodeExpression setMemberResourceProcessCallbackStatementLeft; if (LinkedMember.Length == 0) // no member being referenced setMemberResourceProcessCallbackStatementLeft = resultResourcePropertyReference; else { // TODO: add additional code for fields, methods, and method return results // TODO: allow members with the same name to be processed. MemberInfo member = Linked.Type.GetMember(LinkedMember).First(); switch (member.MemberType) { case MemberTypes.Property: { setMemberResourceProcessCallbackStatementLeft = new CodePropertyReferenceExpression(resultResourcePropertyReference, LinkedMember); } break; case MemberTypes.Field: { setMemberResourceProcessCallbackStatementLeft = new CodeFieldReferenceExpression(resultResourcePropertyReference, LinkedMember); } break; case MemberTypes.Method: { setMemberResourceProcessCallbackStatementLeft = new CodeMethodReferenceExpression(resultResourcePropertyReference, LinkedMember); } break; default: { throw new Exception("Unable to process the linked instance member name. The type of the member is currently not handled."); } } } // This code creates: // this.TheGenericInstance = [the linked to instance or the result from the linked to property, field, method, etc.] CodeAssignStatement assignGenericInstance = new CodeAssignStatement(new CodePropertyReferenceExpression(ThisReference, GenericInstanceName), setMemberResourceProcessCallbackStatementLeft); SetMemberResourceProcessCallback.Statements.Add(assignGenericInstance); } return base.ClassConstructor; } } /// /// A that represents the creation of this /// instance. By default, this property returns the statement for the /// creation of the instance using a default constructor or null if this /// 's does not contain a /// parameterless constructor. /// public override CodeStatement CreationStatement { get { return null; } } /// /// The CodeDom that generates the callback method that sets the /// after it has been loaded. /// public CodeMemberMethod SetMemberResourceProcessCallback { get { if (_setMemberResourceProcessCallback == null) { // Create a method in the member class that sets the member. // This method will take an instance of // IResourceProcessResult so that it can be used // as the callback parameter to // ObjectContentResourceProcessor.From(ResourceProcessCallback, Stream, IOption[]) _setMemberResourceProcessCallback = new CodeMemberMethod(); _setMemberResourceProcessCallback.Name = "SetMemberResourceProcessCallback"; Type resourceProcessorResultType = typeof(IResourceProcessResult<>).MakeGenericType(Linked.Type); _setMemberResourceProcessCallback.Parameters.Add(new CodeParameterDeclarationExpression(resourceProcessorResultType, "result")); TypeDeclaration.Members.Add(_setMemberResourceProcessCallback); InitializeMethod = _setMemberResourceProcessCallback; } return _setMemberResourceProcessCallback; } } /// /// The that this /// links to. /// public IInstance Linked { get { // HACK: We are using the result of this callback immediately. This is a problem // because the result could be returned asynchronously. Right now, that isn't happening // but in the future it could be. Fix this so that asynchronous results are properly // handled. if (_linkedInstance == null) { using (Stream stream = new FileSearchStream(_services, Path, FileMode.Open)) { IOption[] options = Array.Empty; if(LinkedName.Length != 0) options = new IOption[] { new ResourceName(LinkedName) }; ComponentResourceProcessor.From(result => _linkedInstance = (IInstance)result.Resource, stream, options); } } return _linkedInstance; } } /// /// The name of the assembly where this instance is located. /// public override AssemblyName Assembly { get { AssemblyName assembly = Linked.Assembly; if (LinkedMember.Length != 0) assembly = LinkedMemberType.Assembly.GetName(); return assembly; } } /// /// The type of this instance. /// public override Type Type { get { Type type = Linked.Type; if (LinkedMember.Length != 0) type = LinkedMemberType; return type; } } /// /// The of the . /// public Type LinkedMemberType { get { if(_linkedMemberType == null) { MemberInfo member = Linked.Type.GetMember(LinkedMember).First(); switch (member.MemberType) { case MemberTypes.Property: { PropertyInfo property = (PropertyInfo) member; _linkedMemberType = property.PropertyType; } break; case MemberTypes.Field: { FieldInfo field = (FieldInfo)member; _linkedMemberType = field.FieldType; } break; case MemberTypes.Method: { // Either the user is linking to the return value // of the method or they are linking to the method // itself (as an event handler). We'll know once // we've checked the parent. CodeDomEvent parentEvent = Parent as CodeDomEvent; if(parentEvent != null) { EventInfo eventInfo = (EventInfo)parentEvent.Info; _linkedMemberType = eventInfo.EventHandlerType; } else { MethodInfo method = (MethodInfo)member; _linkedMemberType = method.ReturnType; } } break; default: { throw new Exception("Unable to process the linked instance member name. This member type is currently not handled."); } } } return _linkedMemberType; } } /// /// The name of this component which can be used to generate code for /// this component. /// public override string CodeName { get { if (string.IsNullOrEmpty(_codeName) == true) _codeName = Instance.GenerateCodeName(this); return _codeName; } } /// /// The that loads in /// instances. This resource processor is used /// to load in the objects. /// [Inject] public IResourceProcessor ComponentResourceProcessor { set; get; } /// /// The name of the instance that this instance links to. /// string LinkedName { get; set; } /// /// The name of the member, on the instance named /// , that is to be linked to or /// if no member is linked to. /// string LinkedMember { get; set; } /// /// The code that grabs the ObjectContentResourceProcessor from the /// services instance. /// CodeVariableDeclarationStatement _objectResourceProcessorDeclaration; /// /// The instance that gives out services for use by this type and takes /// services from this type for use by other systems. /// IEventCollection _services; /// /// The backing field to . /// CodeMemberMethod _setMemberResourceProcessCallback; /// /// The backing field to . /// IInstance _linkedInstance; /// /// The backing field to . /// string _codeName; /// /// The backing field to . /// Type _linkedMemberType; } }