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