in objectModel/CSharp/Microsoft.CommonDataModel.ObjectModel/ResolvedModel/ResolvedAttributeSetBuilder.cs [379:615]
private List<ResolvedAttribute> GetApplierGeneratedAttributes(AttributeResolutionContext arc, bool clearState, bool applyModifiers)
{
if (this.ResolvedAttributeSet?.Set == null || arc == null)
return null;
AttributeResolutionApplierCapabilities caps = arc.ApplierCaps;
if (caps == null || (!caps.CanAttributeAdd && !caps.CanGroupAdd && !caps.CanRoundAdd))
return null;
List<ResolvedAttribute> resAttOut = new List<ResolvedAttribute>();
// this function constructs a 'plan' for building up the resolved attributes that get generated from a set of traits being applied to
// a set of attributes. it manifests the plan into an array of resolved attributes
// there are a few levels of hierarchy to consider.
// 1. once per set of attributes, the traits may want to generate attributes. this is an attribute that is somehow descriptive of the whole set,
// even if it has repeating patterns, like the count for an expanded array.
// 2. it is possible that some traits (like the array expander) want to keep generating new attributes for some run. each time they do this is considered a 'round'
// the traits are given a chance to generate attributes once per round. every set gets at least one round, so these should be the attributes that
// describe the set of other attributes. for example, the foreign key of a relationship or the 'class' of a polymorphic type, etc.
// 3. for each round, there are new attributes created based on the resolved attributes from the previous round (or the starting atts for this set)
// the previous round attribute need to be 'done' having traits applied before they are used as sources for the current round.
// the goal here is to process each attribute completely before moving on to the next one
// that may need to start out clean
if (clearState)
{
List<ResolvedAttribute> toClear = this.ResolvedAttributeSet.Set;
for (int i = 0; i < toClear.Count; i++)
{
toClear[i].ApplierState = null;
}
}
// make an attribute context to hold attributes that are generated from appliers
// there is a context for the entire set and one for each 'round' of applications that happen
var attCtxContainerGroup = this.ResolvedAttributeSet.AttributeContext;
if (attCtxContainerGroup != null)
{
AttributeContextParameters acp = new AttributeContextParameters
{
under = attCtxContainerGroup,
type = Enums.CdmAttributeContextType.GeneratedSet,
Name = "_generatedAttributeSet"
};
attCtxContainerGroup = CdmAttributeContext.CreateChildUnder(arc.ResOpt, acp);
}
var attCtxContainer = attCtxContainerGroup;
Func<ResolvedAttribute, AttributeResolutionApplier, Func<ApplierContext, bool>, dynamic, string, ApplierContext> MakeResolvedAttribute = (resAttSource, action, queryAdd, doAdd, state) =>
{
ApplierContext appCtx = new ApplierContext
{
State = state,
ResOpt = arc.ResOpt,
AttCtx = attCtxContainer,
ResAttSource = resAttSource,
ResGuide = arc.ResGuide
};
if ((resAttSource?.Target as ResolvedAttributeSet)?.Set != null)
return appCtx; // makes no sense for a group
// will something add
if (queryAdd(appCtx))
{
// may want to make a new attribute group
// make the 'new' attribute look like any source attribute for the duration of this call to make a context. there could be state needed
appCtx.ResAttNew = resAttSource;
if (this.ResolvedAttributeSet.AttributeContext != null && action.WillCreateContext?.Invoke(appCtx) == true)
action.DoCreateContext(appCtx);
// make a new resolved attribute as a place to hold results
appCtx.ResAttNew = new ResolvedAttribute(appCtx.ResOpt, null, null, (CdmAttributeContext)appCtx.AttCtx);
// copy state from source
if (resAttSource?.ApplierState != null)
appCtx.ResAttNew.ApplierState = resAttSource.ApplierState.Copy();
else
appCtx.ResAttNew.ApplierState = new ApplierState();
// if applying traits, then add the sets traits as a starting point
if (applyModifiers)
{
appCtx.ResAttNew.ResolvedTraits = arc.TraitsToApply.DeepCopy();
}
// make it
doAdd(appCtx);
// combine resolution guidence for this set with anything new from the new attribute
appCtx.ResGuideNew = (appCtx.ResGuide as CdmAttributeResolutionGuidance).CombineResolutionGuidance(appCtx.ResGuideNew as CdmAttributeResolutionGuidance);
appCtx.ResAttNew.Arc = new AttributeResolutionContext(arc.ResOpt, appCtx.ResGuideNew as CdmAttributeResolutionGuidance, appCtx.ResAttNew.ResolvedTraits);
if (applyModifiers)
{
// add the sets traits back in to this newly added one
appCtx.ResAttNew.ResolvedTraits = appCtx.ResAttNew.ResolvedTraits.MergeSet(arc.TraitsToApply);
// be sure to use the new arc, the new attribute may have added actions. For now, only modify and remove will get acted on because recursion. ugh.
// do all of the modify traits
if (appCtx.ResAttNew.Arc.ApplierCaps.CanAttributeModify)
{
// modify acts on the source and we should be done with it
appCtx.ResAttSource = appCtx.ResAttNew;
foreach (var modAct in appCtx.ResAttNew.Arc.ActionsModify)
{
if (modAct.WillAttributeModify(appCtx))
modAct.DoAttributeModify(appCtx);
}
}
}
appCtx.ResAttNew.CompleteContext(appCtx.ResOpt);
// tie this new resolved att to the source via lineage
if (appCtx.ResAttNew.AttCtx != null && resAttSource != null && resAttSource.AttCtx != null && resAttSource.ApplierState?.Flex_remove != true)
{
if (resAttSource.AttCtx.Lineage?.Count > 0)
{
foreach (var lineage in resAttSource.AttCtx.Lineage)
{
appCtx.ResAttNew.AttCtx.AddLineage(lineage);
}
}
else
{
appCtx.ResAttNew.AttCtx.AddLineage(resAttSource.AttCtx);
}
}
}
return appCtx;
};
// get the one time atts
if (caps.CanGroupAdd)
{
if (arc.ActionsGroupAdd != null)
{
foreach (var action in arc.ActionsGroupAdd)
{
ApplierContext appCtx = MakeResolvedAttribute(null, action, action.WillGroupAdd, action.DoGroupAdd, "group");
// save it
if (appCtx?.ResAttNew != null)
{
resAttOut.Add(appCtx.ResAttNew);
}
}
}
}
// now starts a repeating pattern of rounds
// first step is to get attribute that are descriptions of the round.
// do this once and then use them as the first entries in the first set of 'previous' atts for the loop
// make an attribute context to hold attributes that are generated from appliers in this round
int round = 0;
if (attCtxContainerGroup != null)
{
AttributeContextParameters acp = new AttributeContextParameters
{
under = attCtxContainerGroup,
type = Enums.CdmAttributeContextType.GeneratedRound,
Name = "_generatedAttributeRound0"
};
attCtxContainer = CdmAttributeContext.CreateChildUnder(arc.ResOpt, acp);
}
List<ResolvedAttribute> resAttsLastRound = new List<ResolvedAttribute>();
if (caps.CanRoundAdd)
{
if (arc.ActionsRoundAdd != null)
{
foreach (var action in arc.ActionsRoundAdd)
{
ApplierContext appCtx = MakeResolvedAttribute(null, action, action.WillRoundAdd, action.DoRoundAdd, "round");
// save it
if (appCtx?.ResAttNew != null)
{
// overall list
resAttOut.Add(appCtx.ResAttNew);
// previous list
resAttsLastRound.Add(appCtx.ResAttNew);
}
}
}
}
// the first per-round set of attributes is the set owned by this object
resAttsLastRound.AddRange(this.ResolvedAttributeSet.Set);
// now loop over all of the previous atts until they all say 'stop'
if (resAttsLastRound.Count > 0)
{
int continues = 0;
do
{
continues = 0;
List<ResolvedAttribute> resAttThisRound = new List<ResolvedAttribute>();
if (caps.CanAttributeAdd)
{
for (int iAtt = 0; iAtt < resAttsLastRound.Count; iAtt++)
{
if (arc.ActionsAttributeAdd != null)
{
foreach (var action in arc.ActionsAttributeAdd)
{
ApplierContext appCtx = MakeResolvedAttribute(resAttsLastRound[iAtt], action, action.WillAttributeAdd, action.DoAttributeAdd, "detail");
// save it
if (appCtx?.ResAttNew != null)
{
// overall list
resAttOut.Add(appCtx.ResAttNew);
resAttThisRound.Add(appCtx.ResAttNew);
if (appCtx.Continue)
continues++;
}
}
}
}
}
resAttsLastRound = resAttThisRound;
round++;
if (attCtxContainerGroup != null)
{
AttributeContextParameters acp = new AttributeContextParameters
{
under = attCtxContainerGroup,
type = Enums.CdmAttributeContextType.GeneratedRound,
Name = $"_generatedAttributeRound{round}"
};
attCtxContainer = CdmAttributeContext.CreateChildUnder(arc.ResOpt, acp);
}
} while (continues > 0);
}
return resAttOut;
}