private string GetIncludePath(ModelWeavingContext context) { var modelTypeDef = context.ModelTypeDef; var relationships = GetEagerRelationships(context, modelTypeDef, string.Empty, new[] { modelTypeDef }); return(string.Join(",", relationships)); }
public void Execute() { PrintIntro(); LoadTypeDefinitions(); FindModels(); foreach (var modelTypeDef in _modelTypeDefs) { LogInfo($"Weaving type {modelTypeDef.FullName}..."); var context = new ModelWeavingContext( modelTypeDef, LogDebug, LogInfo, LogWarning, LogWarningPoint, LogError, LogWarningPoint); AddSessionField(context); AddIncludePathField(context); AddSessionManagedProperty(context); WeaveId(context); if (context.IdPropDef != null) { AddResourceIdentifierProperty(context); AddInitialize(context); AddStaticCtor(context); WeaveMeta(context); WeaveHasOneIds(context); WeaveHasOnes(context); WeaveHasManyIds(context); WeaveHasManys(context); } } }
private void AddSessionField(ModelWeavingContext context) { context.SessionField = AddField( "session", _sessionTypeDef, FieldAttributes.Private, context); }
private void AddIncludePathField(ModelWeavingContext context) { context.IncludePathField = AddField( "include", TypeSystem.String, FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly, context); }
private void WeaveId(ModelWeavingContext context) { if (context.IdPropDef != null && context.IdPropDef.PropertyType.Resolve() != context.ImportReference(typeof(Guid)).Resolve()) { LogError($"[Id] property must have a {typeof(Guid).FullName} getter: {context.IdPropDef.FullName}"); } // if id property doesn't have a setter, try to add one if (context.IdPropDef != null) { LogInfo($"Upserting [Id] property setter: {context.IdPropDef.FullName} "); var idBackingField = context .IdPropDef ?.GetMethod ?.Body ?.Instructions ?.SingleOrDefault(x => x.OpCode == OpCodes.Ldfld) ?.Operand as FieldReference; var setter = context.IdPropDef.SetMethod; if (setter == null) { setter = new MethodDefinition( $"set_{context.IdPropDef.Name}", MethodAttributes.Private | MethodAttributes.SpecialName | MethodAttributes.HideBySig, context.ImportReference(TypeSystem.Void)); setter.Parameters.Add( new ParameterDefinition( "value", ParameterAttributes.None, context.IdPropDef.PropertyType)); setter.SemanticsAttributes = MethodSemanticsAttributes.Setter; context.Methods.Add(setter); context.IdPropDef.SetMethod = setter; } else { setter.Body.Instructions.Clear(); } var proc = setter.Body.GetILProcessor(); var ret = proc.Create(OpCodes.Ret); proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Callvirt, context.SessionManagedProperty.GetMethod); proc.Emit(OpCodes.Brtrue_S, ret); proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Ldarg_1); // load 'value' onto stack proc.Emit(OpCodes.Stfld, idBackingField); // this.backingField = value; proc.Append(ret); // return LogInfo($"Successfully added updated setter for {context.IdPropDef}"); } }
//TODO: this might be the fugliest algo ever written. maybe clean this up private IEnumerable <string> GetEagerRelationships( ModelWeavingContext context, TypeDefinition type, string path, TypeDefinition[] pathTypes) { var eagerRltns = type.Properties .Where(x => x.CustomAttributes .Where(attr => attr.AttributeType.Resolve() == context.ImportReference(_hasOneAttributeTypeDef).Resolve() || attr.AttributeType.Resolve() == context.ImportReference(_hasManyAttributeTypeDef).Resolve()) .Where(attr => attr.HasConstructorArguments) .SelectMany(attr => attr.ConstructorArguments .Where(arg => arg.Type.Resolve() == context.ImportReference(_loadStrategyTypeDef).Resolve())) .Any(arg => (int)arg.Value == 1)) // eager .Where(eagerProp => { var eagerPropAttr = eagerProp.CustomAttributes.ContainsAttribute(Constants.Attributes.HasOne) ? Constants.Attributes.HasOne : Constants.Attributes.HasMany; var eagerPropName = eagerProp.JsonApiName(TypeSystem, eagerPropAttr); var eagerPropType = eagerProp.PropertyType.Resolve(); var nextPath = string.IsNullOrEmpty(path) ? eagerPropName : $"{path}.{eagerPropName}"; var typeVisited = pathTypes.Contains(eagerPropType); if (typeVisited) { LogWarning($"Potential circular reference detected and omitted from eager load: {eagerProp.PropertyType.Resolve().FullName}::{nextPath}"); } return(!typeVisited); }); if (eagerRltns.Any()) { return(eagerRltns.SelectMany(x => { var eagerPropAttr = x.CustomAttributes.ContainsAttribute(Constants.Attributes.HasOne) ? Constants.Attributes.HasOne : Constants.Attributes.HasMany; var eagerPropName = x.JsonApiName(TypeSystem, eagerPropAttr); var eagerPropType = x.PropertyType.Resolve(); var nextPath = string.IsNullOrEmpty(path) ? eagerPropName : $"{path}.{eagerPropName}"; return GetEagerRelationships( context, x.PropertyType.Resolve(), nextPath, pathTypes.Concat(new[] { eagerPropType }).ToArray()); }) .ToArray()); } return(new[] { path }); }
private void WeaveMeta(ModelWeavingContext context) { foreach (var propertyDef in context.MappedMeta) { if (propertyDef.CustomAttributes.ContainsAttribute(Constants.Attributes.Property)) { LogError($"Property {propertyDef.FullName} cannot be included in both attributes and meta"); } } }
private void AddSessionManagedProperty(ModelWeavingContext context) { var propertyName = "__argo__generated_SessionManaged"; var getter = new MethodDefinition( $"get_{propertyName}", MethodAttributes.Private | MethodAttributes.SpecialName | MethodAttributes.HideBySig, context.ImportReference(typeof(bool))) { SemanticsAttributes = MethodSemanticsAttributes.Getter }; var proc = getter.Body.GetILProcessor(); proc.Body.Variables.Add(new VariableDefinition(context.ImportReference(typeof(bool)))); var load0 = proc.Create(OpCodes.Ldc_I4_0); var storeLoc0 = proc.Create(OpCodes.Stloc_0); var loadRet = proc.Create(OpCodes.Ldloc_0); proc.Emit(OpCodes.Nop); //====== this.__argo__generated_session != null proc.Emit(OpCodes.Ldarg_0); proc.Emit(OpCodes.Ldfld, context.SessionField); proc.Emit(OpCodes.Brfalse_S, load0); //============================================= //====== !this.__argo__generated_session.Disposed proc.Emit(OpCodes.Ldarg_0); // push 'this' onto stack proc.Emit(OpCodes.Ldfld, context.SessionField); // push 'this.__argo__generated_session' onto stack proc.Emit(OpCodes.Callvirt, context.ImportReference(_session_DisposedGetter)); // push 'Disposed' value onto stack proc.Emit(OpCodes.Ldc_I4_0); proc.Emit(OpCodes.Ceq); proc.Emit(OpCodes.Br_S, storeLoc0); //=============================================== proc.Append(load0); proc.Append(storeLoc0); proc.Emit(OpCodes.Br_S, loadRet); proc.Append(loadRet); proc.Emit(OpCodes.Ret); context.SessionManagedProperty = new PropertyDefinition( propertyName, PropertyAttributes.None, context.ImportReference(typeof(bool))) { GetMethod = getter }; context.Methods.Add(getter); context.Properties.Add(context.SessionManagedProperty); }
private void WeaveReferenceGetter( ModelWeavingContext context, FieldReference backingField, FieldReference backingFieldInitialized, PropertyDefinition refPropDef, string attrName) { // supply generic type arguments to template var sessionGetAttr = _session_GetReference.MakeGenericMethod(context.ModelTypeDef, refPropDef.PropertyType); // get // { // if (this.__argo__generated_session != null && !this.<[PropName]>k__BackingFieldInitialized) // { // this.<[PropName]>k__BackingField = this.__argo__generated_session.GetReference<[ModelType], [ReturnType]>(this, "[AttrName]"); // this.<[PropName]>k__BackingFieldInitialized = true; // } // return this.<[PropName]>k__BackingField; // } refPropDef.GetMethod.Body.Instructions.Clear(); var proc = refPropDef.GetMethod.Body.GetILProcessor(); var returnField = proc.Create(OpCodes.Ldarg_0); proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Ldfld, context.SessionField); // load __argo__generated_session field from 'this' proc.Emit(OpCodes.Brfalse, returnField); // if __argo__generated_session != null continue, else returnField proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Ldfld, backingFieldInitialized); // load <[PropName]>k__BackingFieldInitialized from 'this' proc.Emit(OpCodes.Brtrue, returnField); // !this.<[PropName]>k__BackingFieldInitialized continue, else returnField proc.Emit(OpCodes.Ldarg_0); // load 'this' to reference backing field proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack to reference session field proc.Emit(OpCodes.Ldfld, context.SessionField); // load __argo__generated_session field from 'this' proc.Emit(OpCodes.Ldarg_0); // load 'this' proc.Emit(OpCodes.Ldstr, attrName); // load attrName onto stack proc.Emit(OpCodes.Callvirt, context.ImportReference( sessionGetAttr, refPropDef.PropertyType.IsGenericParameter ? context.ModelTypeDef : null)); // invoke session.GetReference(..) proc.Emit(OpCodes.Stfld, backingField); // store return value in 'this'.<backing field> proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Ldc_I4_1); // load true (1) onto stack proc.Emit(OpCodes.Stfld, backingFieldInitialized); // store true in 'this'.<backing field>Initialized proc.Append(returnField); // load 'this' onto stack proc.Emit(OpCodes.Ldfld, backingField); // load 'this'.<backing field> proc.Emit(OpCodes.Ret); // return }
private static FieldDefinition AddField( string fieldName, TypeReference fieldType, FieldAttributes attributes, ModelWeavingContext context) { var fieldDef = new FieldDefinition( $"__argo__generated_{fieldName}", attributes, context.ImportReference(fieldType)); context.Fields.Add(fieldDef); return(fieldDef); }
private void WeaveHasManys(ModelWeavingContext context) { if (_session_GetGenericEnumerable == null || _session_GetGenericCollection == null) { throw new Exception("Argo relationship weaving failed unexpectedly"); } foreach (var propertyDef in context.MappedHasManys) { var propertyTypeRef = propertyDef.PropertyType; var propertyTypeDef = propertyTypeRef.Resolve(); MethodReference getRltnMethRef; if (propertyTypeDef == context.ImportReference(typeof(IEnumerable <>)).Resolve()) { getRltnMethRef = _session_GetGenericEnumerable; } else if (propertyTypeDef == context.ImportReference(typeof(ICollection <>)).Resolve()) { getRltnMethRef = _session_GetGenericCollection; } else { LogError($"Argo encountered a HasMany relationship on non IEnumerable<T> or ICollection<T> property {propertyDef.FullName}"); continue; } // get the backing field var backingField = propertyDef.BackingField(); if (backingField == null) { LogError($"Failed to load backing field for property {propertyDef.FullName}"); continue; } // find the rltnName, if there is one var rltnName = propertyDef.JsonApiName(TypeSystem, Constants.Attributes.HasMany); // find property generic element type var elementTypeDef = ((GenericInstanceType)propertyTypeRef).GenericArguments.First().Resolve(); LogInfo($"\tWeaving {propertyDef} => {rltnName}"); WeaveRltnGetter(context, backingField, propertyDef, elementTypeDef, getRltnMethRef, rltnName); } }
private static void WeaveRltnGetter( ModelWeavingContext context, FieldReference backingField, PropertyDefinition rltnPropDef, TypeReference elementTypeDef, MethodReference sessionGetRltnGeneric, string rltnName) { // supply generic type args to template var sessionGetRltn = sessionGetRltnGeneric.MakeGenericMethod( context.ModelTypeDef, elementTypeDef); rltnPropDef.GetMethod.Body.Instructions.Clear(); var proc = rltnPropDef.GetMethod.Body.GetILProcessor(); var endif = proc.Create(OpCodes.Ldarg_0); // TODO: this isn't thread safe - consider generating a Lazy in the ctor and invoking it here proc.Emit(OpCodes.Ldarg_0); proc.Emit(OpCodes.Ldfld, context.SessionField); proc.Emit(OpCodes.Brfalse_S, endif); proc.Emit(OpCodes.Ldarg_0); proc.Emit(OpCodes.Ldfld, backingField); proc.Emit(OpCodes.Brtrue_S, endif); proc.Emit(OpCodes.Ldarg_0); proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack to reference session field proc.Emit(OpCodes.Ldfld, context.SessionField); // load __argo__generated_session field from 'this' proc.Emit(OpCodes.Ldarg_0); // load 'this' proc.Emit(OpCodes.Ldstr, rltnName); // load attrName onto stack proc.Emit(OpCodes.Callvirt, context.ImportReference( sessionGetRltn, rltnPropDef.PropertyType.IsGenericParameter ? context.ModelTypeDef : null)); // invoke session.GetAttribute(..) proc.Emit(OpCodes.Stfld, backingField); // store return value in 'this'.<backing field> proc.Append(endif); proc.Emit(OpCodes.Ldfld, backingField); proc.Emit(OpCodes.Ret); }
private void WeaveHasManyIds(ModelWeavingContext context) { if (_session_GetRelationshipIds == null) { throw new Exception("Argo relationship id weaving failed unexpectedly"); } foreach (var propertyDef in context.MappedHasManyIds) { if (propertyDef.PropertyType.Resolve() != context.ImportReference(typeof(IEnumerable)).Resolve() && propertyDef.PropertyType.Resolve() != context.ImportReference(typeof(IEnumerable <Guid>)).Resolve()) { LogError($"[HasManyIds] property must have a {typeof(IEnumerable).FullName} or {typeof(IEnumerable<Guid>).FullName} getter: {propertyDef.FullName}"); } if (propertyDef.SetMethod != null) { LogError($"[HasManyIds] property must not have a setter"); } // get the backing field var backingField = propertyDef ?.GetMethod ?.Body ?.Instructions ?.SingleOrDefault(x => x.OpCode == OpCodes.Ldfld) ?.Operand as FieldReference; if (backingField == null) { throw new Exception($"Failed to load backing field for property {propertyDef.FullName}"); } // find the attrName, if there is one var attrName = propertyDef.JsonApiName(TypeSystem, Constants.Attributes.HasManyIds); LogInfo($"\tWeaving {propertyDef} => {attrName}"); WeaveRelationshipIdsGetter(context, backingField, propertyDef, attrName); } }
private void WeaveAttributeFieldInitializers( ModelWeavingContext context, ILProcessor proc, IEnumerable <PropertyDefinition> attrPropDefs) { foreach (var attrPropDef in attrPropDefs) { // supply generic type arguments to template var sessionGetAttr = _session_GetAttribute .MakeGenericMethod(context.ModelTypeDef, attrPropDef.PropertyType); var backingField = attrPropDef.BackingField(); if (backingField == null) { throw new Exception($"Failed to load backing field for property {attrPropDef?.FullName}"); } var propAttr = attrPropDef.CustomAttributes.GetAttribute(Constants.Attributes.Property); var attrName = propAttr.ConstructorArguments .Select(x => x.Value as string) .SingleOrDefault() ?? attrPropDef.Name.Camelize(); proc.Emit(OpCodes.Ldarg_0); proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack to reference session field proc.Emit(OpCodes.Ldfld, context.SessionField); // load __argo__generated_session field from 'this' proc.Emit(OpCodes.Ldarg_0); // load 'this' proc.Emit(OpCodes.Ldstr, attrName); // load attrName onto stack proc.Emit(OpCodes.Callvirt, context.ImportReference( sessionGetAttr, attrPropDef.PropertyType.IsGenericParameter ? context.ModelTypeDef : null)); // invoke session.GetAttribute(..) proc.Emit(OpCodes.Stfld, backingField); // store return value in 'this'.<backing field> } }
private void WeaveHasOnes(ModelWeavingContext context) { if (_session_GetReference == null) { throw new Exception("Argo relationship weaving failed unexpectedly"); } foreach (var propertyDef in context.MappedHasOnes) { // get the backing field var backingField = propertyDef.BackingField(); if (backingField == null) { throw new Exception($"Failed to load backing field for property {propertyDef.FullName}"); } /* Add a boolean field to track whether the init was attempted. A null check * on the backing field is not good enough since they could have set the value to null. */ var backingFieldInitialized = AddField( backingField.Name + "Initialized", TypeSystem.Boolean, FieldAttributes.Private, context); // find the attrName, if there is one var attrName = propertyDef.JsonApiName(TypeSystem, Constants.Attributes.HasOne); LogInfo($"\tWeaving {propertyDef} => {attrName}"); WeaveReferenceGetter(context, backingField, backingFieldInitialized, propertyDef, attrName); if (propertyDef.SetMethod == null) { return; } WeaveReferenceSetter(backingField, backingFieldInitialized, propertyDef); } }
private void AddStaticCtor(ModelWeavingContext context) { var ctor = context.ModelTypeDef.GetStaticConstructor(); var include = GetIncludePath(context); if (ctor == null) { ctor = new MethodDefinition( ".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, TypeSystem.Void); context.Methods.Add(ctor); var proc = ctor.Body.GetILProcessor(); proc.Emit(OpCodes.Ldstr, include); proc.Emit(OpCodes.Stsfld, context.IncludePathField); proc.Emit(OpCodes.Ret); } else { var proc = ctor.Body.GetILProcessor(); var originalFirst = ctor.Body.Instructions[0]; proc.InsertBefore(originalFirst, proc.Create(OpCodes.Ldstr, include)); proc.InsertBefore(originalFirst, proc.Create(OpCodes.Stsfld, context.IncludePathField)); } context.ModelTypeDef.IsBeforeFieldInit = false; }
private void AddResourceIdentifierProperty(ModelWeavingContext context) { context.ResourcePropDef = AddAutoProperty("__argo__generated_Resource", context); }
private PropertyDefinition AddAutoProperty(string propertyName, ModelWeavingContext context) { var backingField = new FieldDefinition( $"<{propertyName}>k__BackingField", FieldAttributes.Private, context.ImportReference(_resourceIdentifierTypeDef)); backingField.CustomAttributes.Add(new CustomAttribute(context.ImportReference(_compilerGeneratedAttribute))); backingField.CustomAttributes.Add(new CustomAttribute(context.ImportReference(_debuggerBrowsableAttribute)) { ConstructorArguments = { new CustomAttributeArgument( context.ImportReference(_debuggerBrowsableStateTypeDef), DebuggerBrowsableState.Collapsed) } }); var getter = new MethodDefinition( $"get_{propertyName}", MethodAttributes.Private | MethodAttributes.SpecialName | MethodAttributes.HideBySig, context.ImportReference(_resourceIdentifierTypeDef)) { SemanticsAttributes = MethodSemanticsAttributes.Getter }; var getterProc = getter.Body.GetILProcessor(); getterProc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack getterProc.Emit(OpCodes.Ldfld, backingField); // load <__argo__generated_Resource>k__BackingField onto stack getterProc.Emit(OpCodes.Ret); // return this.<__argo__generated_Resource>k__BackingField; var setter = new MethodDefinition( $"set_{propertyName}", MethodAttributes.Private | MethodAttributes.SpecialName | MethodAttributes.HideBySig, context.ImportReference(TypeSystem.Void)) { SemanticsAttributes = MethodSemanticsAttributes.Setter, Parameters = { new ParameterDefinition( "value", ParameterAttributes.None, context.ImportReference(_resourceIdentifierTypeDef)) } }; var setterProc = setter.Body.GetILProcessor(); setterProc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack setterProc.Emit(OpCodes.Ldarg_1); // load 'value' onto stack setterProc.Emit(OpCodes.Stfld, backingField); // this.<__argo__generated_Resource>k__BackingField = value; setterProc.Emit(OpCodes.Ret); // return; var propDef = new PropertyDefinition( propertyName, PropertyAttributes.None, context.ImportReference(_resourceIdentifierTypeDef)) { GetMethod = getter, SetMethod = setter }; context.Fields.Add(backingField); context.Methods.Add(getter); context.Methods.Add(setter); context.Properties.Add(propDef); return(propDef); }
private void AddInitialize(ModelWeavingContext context) { // public void __argo__generated_Initialize(IResourceIdentifier resource, IModelSession session) // { // this.__argo__generated_Resource = resource; // this.__argo__generated_session = session; // this.__argo__generated_includePath = "model.include.path"; // this.Id = __argo__generated_session.GetId<TModel>(); // } var initialize = new MethodDefinition( "__argo__generated_Initialize", MethodAttributes.Public, TypeSystem.Void); initialize.Parameters.Add( new ParameterDefinition( "resource", ParameterAttributes.None, context.ImportReference(_resourceIdentifierTypeDef))); initialize.Parameters.Add( new ParameterDefinition( "session", ParameterAttributes.None, context.ImportReference(_sessionTypeDef))); var idBackingField = context .IdPropDef ?.GetMethod ?.Body ?.Instructions ?.SingleOrDefault(x => x.OpCode == OpCodes.Ldfld) ?.Operand as FieldReference; // supply generic type arguments to template var sessionGetId = _session_GetId.MakeGenericMethod(context.ModelTypeDef); var proc = initialize.Body.GetILProcessor(); proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Ldarg_1); // load arg 'resource' onto stack proc.Emit(OpCodes.Callvirt, context.ResourcePropDef.SetMethod); proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Ldarg_2); // load arg 'session' onto stack proc.Emit(OpCodes.Stfld, context.SessionField); // this.__argo__generated_session = session; proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Ldfld, context.SessionField); // load this.__argo__generated_session proc.Emit(OpCodes.Ldarg_0); // load 'this' onto stack proc.Emit(OpCodes.Callvirt, context.ImportReference(sessionGetId)); proc.Emit(OpCodes.Stfld, idBackingField); // this.<Id>K_backingField = this.__argo__generated_session.GetId<TModel>(); // this._attrBackingField = this.__argo__generated_session.GetAttribute WeaveAttributeFieldInitializers(context, proc, context.MappedAttributes); // this._attrBackingField = this.__argo__generated_session.GetMeta WeaveMetaFieldInitializers(context, proc, context.MappedMeta); proc.Emit(OpCodes.Ret); // return context.Methods.Add(initialize); }