|
// <copyright>
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
namespace System.Activities.DynamicUpdate
{
using System;
using System.Activities.DynamicUpdate;
using System.Activities.Validation;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Runtime;
using System.Runtime.CompilerServices;
public class DynamicUpdateMapBuilder
{
private HashSet<Activity> disallowUpdateInside;
public DynamicUpdateMapBuilder()
{
}
public bool ForImplementation
{
get;
set;
}
public ISet<Activity> DisallowUpdateInside
{
get
{
if (this.disallowUpdateInside == null)
{
this.disallowUpdateInside = new HashSet<Activity>(ReferenceEqualityComparer.Instance);
}
return this.disallowUpdateInside;
}
}
public Func<object, DynamicUpdateMapItem> LookupMapItem
{
get;
set;
}
public Func<Activity, DynamicUpdateMap> LookupImplementationMap
{
get;
set;
}
public LocationReferenceEnvironment UpdatedEnvironment
{
get;
set;
}
public Activity UpdatedWorkflowDefinition
{
get;
set;
}
public LocationReferenceEnvironment OriginalEnvironment
{
get;
set;
}
public Activity OriginalWorkflowDefinition
{
get;
set;
}
// Internal hook to allow DynamicUpdateServices to surface a customized error message when
// there is an invalid activity in the disallowUpdateInsideActivities list
internal Func<Activity, Exception> OnInvalidActivityToBlockUpdate
{
get;
set;
}
// Internal hook to allow DynamicUpdateServices to surface a customized error message when
// there is an invalid activity in the disallowUpdateInsideActivities list
internal Func<Activity, Exception> OnInvalidImplementationMapAssociation
{
get;
set;
}
public DynamicUpdateMap CreateMap()
{
IList<ActivityBlockingUpdate> activitiesBlockingUpdate;
return CreateMap(out activitiesBlockingUpdate);
}
[SuppressMessage(FxCop.Category.Design, FxCop.Rule.AvoidOutParameters, Justification = "Approved Design. Need to return the map and the block list.")]
public DynamicUpdateMap CreateMap(out IList<ActivityBlockingUpdate> activitiesBlockingUpdate)
{
RequireProperty(this.LookupMapItem, "LookupMapItem");
RequireProperty(this.UpdatedWorkflowDefinition, "UpdatedWorkflowDefinition");
RequireProperty(this.OriginalWorkflowDefinition, "OriginalWorkflowDefinition");
Finalizer finalizer = new Finalizer(this);
DynamicUpdateMap result = finalizer.FinalizeUpdate(out activitiesBlockingUpdate);
return result;
}
private static void CacheMetadata(Activity workflowDefinition, LocationReferenceEnvironment environment, ActivityUtilities.ProcessActivityCallback callback, bool forImplementation)
{
IList<ValidationError> validationErrors = null;
ActivityUtilities.CacheRootMetadata(workflowDefinition, environment, ProcessTreeOptions(forImplementation), callback, ref validationErrors);
ActivityValidationServices.ThrowIfViolationsExist(validationErrors);
}
static DynamicUpdateMapEntry GetParentEntry(Activity originalActivity, DynamicUpdateMap updateMap)
{
if (originalActivity.Parent != null && originalActivity.Parent.MemberOf == originalActivity.MemberOf)
{
DynamicUpdateMapEntry parentEntry;
updateMap.TryGetUpdateEntry(originalActivity.Parent.InternalId, out parentEntry);
Fx.Assert(parentEntry != null, "We process in IdSpace order, so we always process parents before their children");
return parentEntry;
}
return null;
}
static IEnumerable<Activity> GetPublicDeclaredChildren(Activity activity, bool includeExpressions)
{
IEnumerable<Activity> result = activity.Children.Concat(
activity.ImportedChildren).Concat(
activity.Delegates.Select(d => d.Handler)).Concat(
activity.ImportedDelegates.Select(d => d.Handler));
if (includeExpressions)
{
result = result.Concat(
activity.RuntimeVariables.Select(v => v.Default)).Concat(
activity.RuntimeArguments.Select(a => a.IsBound ? a.BoundArgument.Expression : null));
}
return result.Where(a => a != null && a.Parent == activity);
}
private static ProcessActivityTreeOptions ProcessTreeOptions(bool forImplementation)
{
return forImplementation ? ProcessActivityTreeOptions.DynamicUpdateOptionsForImplementation : ProcessActivityTreeOptions.DynamicUpdateOptions;
}
private static void RequireProperty(object value, string name)
{
if (value == null)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.UpdateMapBuilderRequiredProperty(name)));
}
}
internal class ReferenceEqualityComparer : IEqualityComparer<object>
{
public static readonly IEqualityComparer<object> Instance = new ReferenceEqualityComparer();
ReferenceEqualityComparer()
{
}
public new bool Equals(object x, object y)
{
return object.ReferenceEquals(x, y);
}
public int GetHashCode(object obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
// Preparer walks the tree and identifies, for each object in the tree, an ID that can be
// attached to an object in the new definition to match it to the equivalent object in the
// old definition.
internal class Preparer
{
private Dictionary<object, DynamicUpdateMapItem> updateableObjects;
private Activity originalProgram;
private LocationReferenceEnvironment originalEnvironment;
private bool forImplementation;
public Preparer(Activity originalProgram, LocationReferenceEnvironment originalEnvironment, bool forImplementation)
{
this.originalProgram = originalProgram;
this.originalEnvironment = originalEnvironment;
this.forImplementation = forImplementation;
}
public Dictionary<object, DynamicUpdateMapItem> Prepare()
{
this.updateableObjects = new Dictionary<object, DynamicUpdateMapItem>(ReferenceEqualityComparer.Instance);
CacheMetadata(this.originalProgram, this.originalEnvironment, null, this.forImplementation);
IdSpace idSpace = GetIdSpace();
if (idSpace != null)
{
for (int i = 1; i <= idSpace.MemberCount; i++)
{
ProcessElement(idSpace[i]);
}
}
return this.updateableObjects;
}
IdSpace GetIdSpace()
{
return this.forImplementation ? this.originalProgram.ParentOf : this.originalProgram.MemberOf;
}
void ProcessElement(Activity currentElement)
{
// Attach the original Activity ID to the activity
// The origin of a variable default is the same as the origin of the variable itself.
// So we don't attach match info for the default, since that would conflict with
// the match info for the variable.
if (currentElement.RelationshipToParent != Activity.RelationshipType.VariableDefault || currentElement.Origin == null)
{
ValidateOrigin(currentElement.Origin, currentElement);
this.updateableObjects[currentElement.Origin ?? currentElement] = new DynamicUpdateMapItem(currentElement.InternalId);
}
// Attach the original variable index to the variable
IList<Variable> variables = currentElement.RuntimeVariables;
for (int i = 0; i < variables.Count; i++)
{
Variable variable = variables[i];
if (string.IsNullOrEmpty(variable.Name))
{
ValidateOrigin(variable.Origin, variable);
this.updateableObjects[variable.Origin ?? variable] = new DynamicUpdateMapItem(currentElement.InternalId, i);
}
}
}
void ValidateOrigin(object origin, object element)
{
if (origin != null)
{
DynamicUpdateMapItem mapItem;
if (this.updateableObjects.TryGetValue(origin, out mapItem))
{
string error = null;
if (mapItem.IsVariableMapItem)
{
Variable dupe = GetVariable(mapItem);
Variable elementVar = element as Variable;
if (elementVar != null)
{
error = SR.DuplicateOriginVariableVariable(origin, dupe.Name, elementVar.Name);
}
else
{
error = SR.DuplicateOriginActivityVariable(origin, element, dupe.Name);
}
}
else
{
Activity dupe = GetActivity(mapItem);
Variable elementVar = element as Variable;
if (elementVar != null)
{
error = SR.DuplicateOriginActivityVariable(origin, dupe, elementVar.Name);
}
else
{
error = SR.DuplicateOriginActivityActivity(origin, dupe, element);
}
}
throw FxTrace.Exception.AsError(new InvalidWorkflowException(error));
}
}
}
Activity GetActivity(DynamicUpdateMapItem mapItem)
{
return GetIdSpace()[mapItem.OriginalId];
}
Variable GetVariable(DynamicUpdateMapItem mapItem)
{
return GetIdSpace()[mapItem.OriginalVariableOwnerId].RuntimeVariables[mapItem.OriginalId];
}
}
// Builds an Update Map given an old and new definition, and matches between them
internal class Finalizer
{
BitArray foundOriginalElements;
DynamicUpdateMapBuilder builder;
DynamicUpdateMap updateMap;
Dictionary<Activity, object> savedOriginalValues;
bool savedOriginalValuesForReferencedChildren;
IList<ActivityBlockingUpdate> blockList;
// dictionary from expression root to the activity that can make it go idle
Dictionary<Activity, Activity> expressionRootsThatCanInduceIdle;
public Finalizer(DynamicUpdateMapBuilder builder)
{
this.builder = builder;
this.savedOriginalValues = new Dictionary<Activity, object>(ReferenceEqualityComparer.Instance);
this.Matcher = new DefinitionMatcher(builder.LookupMapItem);
}
public DynamicUpdateMap FinalizeUpdate(out IList<ActivityBlockingUpdate> blockList)
{
this.updateMap = new DynamicUpdateMap();
this.blockList = new List<ActivityBlockingUpdate>();
// cache metadata of originalProgram
CacheMetadata(this.builder.OriginalWorkflowDefinition, this.builder.OriginalEnvironment, null, this.builder.ForImplementation);
IdSpace originalIdSpace = this.builder.ForImplementation ? this.builder.OriginalWorkflowDefinition.ParentOf : this.builder.OriginalWorkflowDefinition.MemberOf;
if (originalIdSpace == null)
{
Fx.Assert(this.builder.ForImplementation, "An activity must be a member of an IdSpace");
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.InvalidOriginalWorkflowDefinitionForImplementationMapCreation));
}
this.Matcher.OldIdSpace = originalIdSpace;
this.foundOriginalElements = new BitArray(originalIdSpace.MemberCount);
// cache metadata of modifiedProgram before iterative ProcessElement()
CacheMetadata(this.builder.UpdatedWorkflowDefinition, this.builder.UpdatedEnvironment, CheckCanArgumentOrVariableDefaultInduceIdle, this.builder.ForImplementation);
IdSpace idSpace = this.builder.ForImplementation ? this.builder.UpdatedWorkflowDefinition.ParentOf : this.builder.UpdatedWorkflowDefinition.MemberOf;
if (idSpace == null)
{
Fx.Assert(this.builder.ForImplementation, "An activity must be a member of an IdSpace");
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.InvalidUpdatedWorkflowDefinitionForImplementationMapCreation));
}
this.Matcher.NewIdSpace = idSpace;
// check if any of the activities or variables from the original definition
// were reused in the updated definition
for (int i = 1; i < originalIdSpace.MemberCount + 1; i++)
{
CheckForReusedActivity(originalIdSpace[i]);
}
// most of the updatemap construction processing
for (int i = 1; i < idSpace.MemberCount + 1; i++)
{
ProcessElement(idSpace[i]);
}
// if an activity doesn't have an entry by this point, that means it was removed
for (int i = 0; i < this.foundOriginalElements.Count; i++)
{
if (!this.foundOriginalElements[i])
{
DynamicUpdateMapEntry removalEntry = new DynamicUpdateMapEntry(i + 1, 0);
Activity originalActivity = originalIdSpace[i + 1];
removalEntry.Parent = GetParentEntry(originalActivity, this.updateMap);
if (!removalEntry.IsParentRemovedOrBlocked)
{
removalEntry.DisplayName = originalActivity.DisplayName;
}
this.updateMap.AddEntry(removalEntry);
}
}
if (this.builder.ForImplementation)
{
this.updateMap.IsForImplementation = true;
// gather arguments diff between new and old activity definitions
this.updateMap.OldArguments = ArgumentInfo.List(builder.OriginalWorkflowDefinition);
this.updateMap.NewArguments = ArgumentInfo.List(builder.UpdatedWorkflowDefinition);
}
// Validate the Disallow entries
foreach (Activity disallowActivity in this.builder.DisallowUpdateInside)
{
if (disallowActivity == null)
{
continue;
}
if (disallowActivity.MemberOf != idSpace)
{
ThrowInvalidActivityToBlockUpdate(disallowActivity);
}
}
this.updateMap.NewDefinitionMemberCount = idSpace.MemberCount;
blockList = this.blockList;
return this.updateMap;
}
internal bool? AllowUpdateInsideCurrentActivity
{
get;
set;
}
internal string UpdateDisallowedReason
{
get;
set;
}
internal Dictionary<string, object> SavedOriginalValuesForCurrentActivity
{
get;
set;
}
internal DefinitionMatcher Matcher
{
get;
private set;
}
internal Dictionary<Activity, Activity> ExpressionRootsThatCanInduceIdle
{
get
{
return this.expressionRootsThatCanInduceIdle;
}
}
void BlockUpdate(Activity activity, UpdateBlockedReason reason, DynamicUpdateMapEntry entry, string message = null)
{
Fx.Assert(activity.MemberOf == (this.builder.ForImplementation ? activity.RootActivity.ParentOf : activity.RootActivity.MemberOf), "Should have called other overload of BlockUpdate");
BlockUpdate(activity, entry.OldActivityId.ToString(CultureInfo.InvariantCulture), reason, entry, message);
}
internal void BlockUpdate(Activity activity, string originalActivityId, UpdateBlockedReason reason, DynamicUpdateMapEntry entry, string message = null)
{
Fx.Assert(reason != UpdateBlockedReason.NotBlocked, "Invalid block reason");
if (!entry.IsRuntimeUpdateBlocked)
{
entry.BlockReason = reason;
if (reason == UpdateBlockedReason.Custom)
{
entry.BlockReasonMessage = message;
}
entry.ImplementationUpdateMap = null;
this.blockList.Add(new ActivityBlockingUpdate(activity, originalActivityId, message ?? UpdateBlockedReasonMessages.Get(reason)));
}
}
internal void SetOriginalValue(Activity key, object value, bool isReferencedChild)
{
if (isReferencedChild)
{
this.savedOriginalValuesForReferencedChildren = true;
}
else
{
this.savedOriginalValues[key] = value;
}
}
internal object GetSavedOriginalValueFromParent(Activity key)
{
object result = null;
this.savedOriginalValues.TryGetValue(key, out result);
return result;
}
void ProcessElement(Activity currentElement)
{
Activity originalElement = this.Matcher.GetMatch(currentElement);
if (originalElement != null)
{
// this means it's an existing one
DynamicUpdateMapEntry mapEntry = this.CreateMapEntry(currentElement, originalElement);
mapEntry.Parent = GetParentEntry(originalElement, this.updateMap);
if (this.builder.DisallowUpdateInside.Contains(currentElement))
{
mapEntry.IsUpdateBlockedByUpdateAuthor = true;
}
if (originalElement.GetType() != currentElement.GetType())
{
// returned matching activity's type doesn't really match the currentElement
BlockUpdate(currentElement, UpdateBlockedReason.TypeChange, mapEntry,
SR.DUActivityTypeMismatch(currentElement.GetType(), originalElement.GetType()));
}
if (this.DelegateArgumentsChanged(currentElement, originalElement))
{
this.BlockUpdate(currentElement, UpdateBlockedReason.DelegateArgumentChange, mapEntry);
}
DynamicUpdateMap implementationMap = null;
if (this.builder.LookupImplementationMap != null)
{
implementationMap = this.builder.LookupImplementationMap(currentElement);
}
// fill ArgumentEntries
// get arguments diff info from implementation map if it exists
// we do this before user participation, so that we don't call into user code
// if the update is invalid
IList<ArgumentInfo> oldArguments = GetOriginalArguments(mapEntry, implementationMap, currentElement, originalElement);
if (oldArguments != null)
{
CreateArgumentEntries(mapEntry, currentElement.RuntimeArguments, oldArguments);
}
// Capture any saved original value associated with this activity by its parent
mapEntry.SavedOriginalValueFromParent = GetSavedOriginalValueFromParent(currentElement);
if (mapEntry.IsRuntimeUpdateBlocked)
{
// don't allow activity to participate if update isn't possible anyway
mapEntry.EnvironmentUpdateMap = null;
return;
}
OnCreateDynamicUpdateMap(currentElement, originalElement, mapEntry, this.Matcher);
if (mapEntry.IsRuntimeUpdateBlocked)
{
// if the activity disabled update, we can't rely on the variable matches,
// so no point in proceeding
mapEntry.EnvironmentUpdateMap = null;
return;
}
// variable entries need to be calculated after activity participation, since
// the activity can participate in matching them
CreateVariableEntries(false, mapEntry, currentElement.RuntimeVariables, originalElement.RuntimeVariables, originalElement);
CreateVariableEntries(true, mapEntry, currentElement.ImplementationVariables, originalElement.ImplementationVariables, originalElement);
if (mapEntry.HasEnvironmentUpdates)
{
FillEnvironmentMapMemberCounts(mapEntry.EnvironmentUpdateMap, currentElement, originalElement, oldArguments);
}
else
{
Fx.Assert(originalElement.SymbolCount == currentElement.SymbolCount ||
originalElement.ImplementationVariables.Count != currentElement.ImplementationVariables.Count,
"Should have environment update if symbol count changed");
}
if (!mapEntry.IsParentRemovedOrBlocked && !mapEntry.IsUpdateBlockedByUpdateAuthor)
{
NestedIdSpaceFinalizer nestedFinalizer = new NestedIdSpaceFinalizer(this, implementationMap, currentElement, originalElement, null);
nestedFinalizer.ValidateOrCreateImplementationMap(mapEntry);
}
}
}
internal static void FillEnvironmentMapMemberCounts(EnvironmentUpdateMap envMap, Activity currentElement, Activity originalElement, IList<ArgumentInfo> oldArguments)
{
envMap.NewVariableCount = currentElement.RuntimeVariables != null ? currentElement.RuntimeVariables.Count : 0;
envMap.NewPrivateVariableCount = currentElement.ImplementationVariables != null ? currentElement.ImplementationVariables.Count : 0;
envMap.NewArgumentCount = currentElement.RuntimeArguments != null ? currentElement.RuntimeArguments.Count : 0;
envMap.OldVariableCount = originalElement.RuntimeVariables.Count;
envMap.OldPrivateVariableCount = originalElement.ImplementationVariables.Count;
envMap.OldArgumentCount = oldArguments != null ? oldArguments.Count : 0;
Fx.Assert((originalElement.HandlerOf == null && currentElement.HandlerOf == null)
|| (originalElement.HandlerOf.RuntimeDelegateArguments.Count == currentElement.HandlerOf.RuntimeDelegateArguments.Count),
"RuntimeDelegateArguments count must not have changed.");
envMap.RuntimeDelegateArgumentCount = originalElement.HandlerOf == null ? 0 : originalElement.HandlerOf.RuntimeDelegateArguments.Count;
}
DynamicUpdateMapEntry CreateMapEntry(Activity currentActivity, Activity matchingOriginal)
{
Fx.Assert(currentActivity != null && matchingOriginal != null, "this entry creation is only for existing activity's ID change.");
this.foundOriginalElements[matchingOriginal.InternalId - 1] = true;
DynamicUpdateMapEntry entry = new DynamicUpdateMapEntry(matchingOriginal.InternalId, currentActivity.InternalId);
this.updateMap.AddEntry(entry);
return entry;
}
internal void OnCreateDynamicUpdateMap(Activity currentElement, Activity originalElement,
DynamicUpdateMapEntry mapEntry, IDefinitionMatcher matcher)
{
this.AllowUpdateInsideCurrentActivity = null;
this.UpdateDisallowedReason = null;
this.SavedOriginalValuesForCurrentActivity = null;
this.savedOriginalValuesForReferencedChildren = false;
currentElement.OnInternalCreateDynamicUpdateMap(this, matcher, originalElement);
if (this.AllowUpdateInsideCurrentActivity == false)
{
this.BlockUpdate(currentElement, originalElement.Id, UpdateBlockedReason.Custom, mapEntry, this.UpdateDisallowedReason);
}
if (this.SavedOriginalValuesForCurrentActivity != null && this.SavedOriginalValuesForCurrentActivity.Count > 0)
{
mapEntry.SavedOriginalValues = this.SavedOriginalValuesForCurrentActivity;
}
if (this.savedOriginalValuesForReferencedChildren)
{
this.BlockUpdate(currentElement, originalElement.Id, UpdateBlockedReason.SavedOriginalValuesForReferencedChildren, mapEntry);
}
}
void CreateVariableEntries(bool forImplementationVariables, DynamicUpdateMapEntry mapEntry, IList<Variable> newVariables, IList<Variable> oldVariables, Activity originalElement)
{
if (newVariables != null && newVariables.Count > 0)
{
for (int i = 0; i < newVariables.Count; i++)
{
Variable newVariable = newVariables[i];
int originalIndex = this.Matcher.GetMatchIndex(newVariable, originalElement, forImplementationVariables);
if (originalIndex != i)
{
EnsureEnvironmentUpdateMap(mapEntry);
EnvironmentUpdateMapEntry environmentEntry = new EnvironmentUpdateMapEntry
{
OldOffset = originalIndex,
NewOffset = i,
};
if (forImplementationVariables)
{
mapEntry.EnvironmentUpdateMap.PrivateVariableEntries.Add(environmentEntry);
}
else
{
mapEntry.EnvironmentUpdateMap.VariableEntries.Add(environmentEntry);
}
if (originalIndex == EnvironmentUpdateMapEntry.NonExistent)
{
Activity idleActivity = GetIdleActivity(newVariable.Default);
if (idleActivity != null)
{
// If an variable default expression goes idle, the activity it is declared on can potentially
// resume execution before the default expression is evaluated. We can't allow that.
this.BlockUpdate(newVariable.Owner, UpdateBlockedReason.AddedIdleExpression, mapEntry,
SR.AddedIdleVariableDefaultBlockDU(newVariable.Name, idleActivity));
}
else if (newVariable.IsHandle)
{
this.BlockUpdate(newVariable.Owner, UpdateBlockedReason.NewHandle, mapEntry);
}
environmentEntry.IsNewHandle = newVariable.IsHandle;
}
}
}
}
// We don't normally create entries for removals, but we need to ensure that
// environment update happens if there are only removals.
if (oldVariables != null && (newVariables == null || newVariables.Count < oldVariables.Count))
{
EnsureEnvironmentUpdateMap(mapEntry);
}
}
internal void CreateArgumentEntries(DynamicUpdateMapEntry mapEntry, IList<RuntimeArgument> newArguments, IList<ArgumentInfo> oldArguments)
{
RuntimeArgument newIdleArgument;
Activity idleActivity;
if (!CreateArgumentEntries(mapEntry, newArguments, oldArguments, this.expressionRootsThatCanInduceIdle, out newIdleArgument, out idleActivity))
{
// If an argument expression goes idle, the activity it is declared on can potentially
// resume execution before the argument is evaluated. We can't allow that.
this.BlockUpdate(newIdleArgument.Owner, UpdateBlockedReason.AddedIdleExpression, mapEntry,
SR.AddedIdleArgumentBlockDU(newIdleArgument.Name, idleActivity));
return;
}
}
// if it detects any added argument whose Expression can induce idle, it returns FALSE along with newIdleArgument and idleActivity. Return true otherwise.
internal static bool CreateArgumentEntries(DynamicUpdateMapEntry mapEntry, IList<RuntimeArgument> newArguments, IList<ArgumentInfo> oldArguments, Dictionary<Activity, Activity> expressionRootsThatCanInduceIdle, out RuntimeArgument newIdleArgument, out Activity idleActivity)
{
newIdleArgument = null;
idleActivity = null;
if (newArguments != null && newArguments.Count > 0)
{
for (int i = 0; i < newArguments.Count; i++)
{
RuntimeArgument newArgument = newArguments[i];
int oldIndex = oldArguments.IndexOf(new ArgumentInfo(newArgument));
Fx.Assert(oldIndex >= 0 || oldIndex == EnvironmentUpdateMapEntry.NonExistent, "NonExistent constant should be consistent with IndexOf");
if (oldIndex != i)
{
EnsureEnvironmentUpdateMap(mapEntry);
mapEntry.EnvironmentUpdateMap.ArgumentEntries.Add(new EnvironmentUpdateMapEntry
{
OldOffset = oldIndex,
NewOffset = i
});
if (oldIndex == EnvironmentUpdateMapEntry.NonExistent && newArgument.IsBound)
{
Activity expressionRoot = newArgument.BoundArgument.Expression;
if (expressionRoot != null && expressionRootsThatCanInduceIdle != null && expressionRootsThatCanInduceIdle.TryGetValue(expressionRoot, out idleActivity))
{
newIdleArgument = newArgument;
return false;
}
}
}
}
}
// We don't normally create entries for removals, but we need to ensure that
// environment update happens if there are only removals.
if (oldArguments != null && (newArguments == null || newArguments.Count < oldArguments.Count))
{
EnsureEnvironmentUpdateMap(mapEntry);
}
return true;
}
IList<ArgumentInfo> GetOriginalArguments(DynamicUpdateMapEntry mapEntry, DynamicUpdateMap implementationMap, Activity updatedActivity, Activity originalActivity)
{
bool argumentsChangedFromImplementationMap = false;
if (implementationMap != null && !implementationMap.ArgumentsAreUnknown)
{
argumentsChangedFromImplementationMap = !ActivityComparer.ListEquals(implementationMap.NewArguments, implementationMap.OldArguments);
bool dynamicArgumentsDetected = !ActivityComparer.ListEquals(ArgumentInfo.List(updatedActivity), implementationMap.NewArguments);
if (argumentsChangedFromImplementationMap && dynamicArgumentsDetected)
{
// this is to ensure no dynamic arguments were added, removed or rearranged as the arguments owning activity was being consumed
// at the same time the activity has arguments changed from its implementation map.
// the list of RuntimeArguments obtained from the configured activity and the list of ArgumentInfos obtained from
// the implementation map must match exactly. Otherwise this activity is blocked for update.
this.BlockUpdate(updatedActivity, UpdateBlockedReason.DynamicArguments, mapEntry, SR.NoDynamicArgumentsInActivityDefinitionChange);
return null;
}
}
return argumentsChangedFromImplementationMap ? implementationMap.OldArguments : ArgumentInfo.List(originalActivity);
}
Activity GetIdleActivity(Activity expressionRoot)
{
Activity result = null;
if (expressionRoot != null && this.expressionRootsThatCanInduceIdle != null)
{
this.expressionRootsThatCanInduceIdle.TryGetValue(expressionRoot, out result);
}
return result;
}
static void EnsureEnvironmentUpdateMap(DynamicUpdateMapEntry mapEntry)
{
if (!mapEntry.HasEnvironmentUpdates)
{
mapEntry.EnvironmentUpdateMap = new EnvironmentUpdateMap();
}
}
void CheckForReusedActivity(Activity activity)
{
if (activity.RootActivity != this.builder.OriginalWorkflowDefinition)
{
throw FxTrace.Exception.AsError(new InvalidWorkflowException(SR.OriginalActivityReusedInModifiedDefinition(activity)));
}
IList<Variable> variables = activity.RuntimeVariables;
for (int i = 0; i < variables.Count; i++)
{
if (variables[i].Owner.RootActivity != this.builder.OriginalWorkflowDefinition)
{
throw FxTrace.Exception.AsError(new InvalidWorkflowException(SR.OriginalVariableReusedInModifiedDefinition(variables[i].Name)));
}
}
}
void CheckCanArgumentOrVariableDefaultInduceIdle(ActivityUtilities.ChildActivity childActivity, ActivityUtilities.ActivityCallStack parentChain)
{
Activity activity = childActivity.Activity;
if (!(activity.IsExpressionRoot || activity.RelationshipToParent == Activity.RelationshipType.VariableDefault))
{
return;
}
if (activity.HasNonEmptySubtree)
{
ActivityUtilities.FinishCachingSubtree(
childActivity, parentChain, ProcessTreeOptions(this.builder.ForImplementation),
(a, c) => CheckCanActivityInduceIdle(activity, a.Activity));
}
else
{
CheckCanActivityInduceIdle(activity, activity);
}
}
void CheckCanActivityInduceIdle(Activity activity, Activity expressionRoot)
{
if (activity.InternalCanInduceIdle)
{
if (this.expressionRootsThatCanInduceIdle == null)
{
this.expressionRootsThatCanInduceIdle = new Dictionary<Activity, Activity>(ReferenceEqualityComparer.Instance);
}
if (!this.expressionRootsThatCanInduceIdle.ContainsKey(expressionRoot))
{
this.expressionRootsThatCanInduceIdle.Add(expressionRoot, activity);
}
}
}
bool DelegateArgumentsChanged(Activity newActivity, Activity oldActivity)
{
// check DelegateArguments of ActivityDelegate owning the handler
if (newActivity.HandlerOf == null)
{
Fx.Assert(oldActivity.HandlerOf == null, "Once two activities have been matched, either both must be handlers or both must not be handlers.");
return false;
}
Fx.Assert(oldActivity.HandlerOf != null, "Once two activities have been matched, either both must be handlers or both must not be handlers.");
return !ActivityComparer.ListEquals(newActivity.HandlerOf.RuntimeDelegateArguments, oldActivity.HandlerOf.RuntimeDelegateArguments);
}
void ThrowInvalidActivityToBlockUpdate(Activity activity)
{
Exception exception;
if (builder.OnInvalidActivityToBlockUpdate != null)
{
exception = builder.OnInvalidActivityToBlockUpdate(activity);
}
else
{
exception = new InvalidOperationException(SR.InvalidActivityToBlockUpdate(activity));
}
throw FxTrace.Exception.AsError(exception);
}
internal void ThrowInvalidImplementationMapAssociation(Activity activity)
{
Exception exception;
if (builder.OnInvalidImplementationMapAssociation != null)
{
exception = builder.OnInvalidImplementationMapAssociation(activity);
}
else
{
exception = new InvalidOperationException(SR.InvalidImplementationMapAssociation(activity));
}
throw FxTrace.Exception.AsError(exception);
}
}
internal interface IDefinitionMatcher
{
void AddMatch(Activity newChild, Activity oldChild, Activity source);
void AddMatch(Variable newVariable, Variable oldVariable, Activity source);
Activity GetMatch(Activity newActivity);
Variable GetMatch(Variable newVariable);
}
internal class DefinitionMatcher : IDefinitionMatcher
{
Dictionary<object, object> newToOldMatches;
Func<object, DynamicUpdateMapItem> matchInfoLookup;
internal DefinitionMatcher(Func<object, DynamicUpdateMapItem> matchInfoLookup)
{
this.matchInfoLookup = matchInfoLookup;
this.newToOldMatches = new Dictionary<object, object>(ReferenceEqualityComparer.Instance);
}
internal IdSpace NewIdSpace
{
get;
set;
}
internal IdSpace OldIdSpace
{
get;
set;
}
// The following methods are intended to be called by the activity author
// (via UpdateMapMetadata), and should validate accordingly
public void AddMatch(Activity newChild, Activity oldChild, Activity source)
{
Fx.Assert(source != null, "source cannot be null.");
if (newChild.Parent != source)
{
throw FxTrace.Exception.Argument("newChild", SR.AddMatchActivityNewParentMismatch(
source, newChild, newChild.Parent));
}
if (newChild.MemberOf != newChild.Parent.MemberOf)
{
throw FxTrace.Exception.Argument("newChild", SR.AddMatchActivityPrivateChild(newChild));
}
if (oldChild.Parent != null && oldChild.MemberOf != oldChild.Parent.MemberOf)
{
throw FxTrace.Exception.Argument("oldChild", SR.AddMatchActivityPrivateChild(oldChild));
}
if (!ParentsMatch(newChild, oldChild))
{
throw FxTrace.Exception.Argument("oldChild", SR.AddMatchActivityNewAndOldParentMismatch(
newChild, oldChild, newChild.Parent, oldChild.Parent));
}
// Only one updated activity can match a given original activity
foreach (Activity newSibling in GetPublicDeclaredChildren(newChild.Parent, true))
{
if (GetMatch(newSibling) == oldChild)
{
this.newToOldMatches[newSibling] = null;
break;
}
}
this.newToOldMatches[newChild] = oldChild;
}
public void AddMatch(Variable newVariable, Variable oldVariable, Activity source)
{
if (!ActivityComparer.SignatureEquals(newVariable, oldVariable))
{
throw FxTrace.Exception.Argument("newVariable", SR.AddMatchVariableSignatureMismatch(
source, newVariable.Name, newVariable.Type, newVariable.Modifiers, oldVariable.Name, oldVariable.Type, oldVariable.Modifiers));
}
if (newVariable.Owner != source)
{
throw FxTrace.Exception.Argument("newVariable", SR.AddMatchVariableNewParentMismatch(
source, newVariable.Name, newVariable.Owner));
}
if (GetMatch(newVariable.Owner) != oldVariable.Owner)
{
throw FxTrace.Exception.Argument("oldVariable", SR.AddMatchVariableNewAndOldParentMismatch(
newVariable.Name, oldVariable.Name, newVariable.Owner, oldVariable.Owner));
}
if (!newVariable.IsPublic)
{
throw FxTrace.Exception.Argument("newVariable", SR.AddMatchVariablePrivateChild(newVariable.Name));
}
if (!oldVariable.IsPublic)
{
throw FxTrace.Exception.Argument("oldVariable", SR.AddMatchVariablePrivateChild(oldVariable.Name));
}
// Only one updated variable can match a given original variable
foreach (Variable newSibling in newVariable.Owner.RuntimeVariables)
{
if (GetMatch(newSibling) == oldVariable)
{
this.newToOldMatches[newSibling] = EnvironmentUpdateMapEntry.NonExistent;
break;
}
}
this.newToOldMatches[newVariable] = oldVariable.Owner.RuntimeVariables.IndexOf(oldVariable);
}
public Activity GetMatch(Activity newChild)
{
object result;
if (this.newToOldMatches.TryGetValue(newChild, out result))
{
return (Activity)result;
}
if (newChild.MemberOf != this.NewIdSpace)
{
// We can only match the IdSpace being updated.
return null;
}
if (newChild.Origin != null && newChild.RelationshipToParent == Activity.RelationshipType.VariableDefault)
{
// Auto-generated variable defaults have the same origin as the variable itself,
// so the match info comes from the variable.
foreach (Variable variable in newChild.Parent.RuntimeVariables)
{
if (variable.Default == newChild)
{
Variable originalVariable = GetMatch(variable);
if (originalVariable != null && originalVariable.Origin != null)
{
return originalVariable.Default;
}
}
}
return null;
}
DynamicUpdateMapItem matchInfo = this.matchInfoLookup(newChild.Origin ?? newChild);
if (matchInfo == null || matchInfo.IsVariableMapItem)
{
return null;
}
Activity originalActivity = this.OldIdSpace[matchInfo.OriginalId];
if (originalActivity != null && ParentsMatch(newChild, originalActivity))
{
this.newToOldMatches.Add(newChild, originalActivity);
return originalActivity;
}
else
{
return null;
}
}
public Variable GetMatch(Variable newVariable)
{
Activity matchingOwner = GetMatch(newVariable.Owner);
if (matchingOwner == null)
{
return null;
}
int index = GetMatchIndex(newVariable, matchingOwner, false);
if (index >= 0)
{
return matchingOwner.RuntimeVariables[index];
}
return null;
}
// return -1 if there is no match
internal int GetMatchIndex(Variable newVariable, Activity matchingOwner, bool forImplementation)
{
object result;
if (this.newToOldMatches.TryGetValue(newVariable, out result))
{
return (int)result;
}
IList<Variable> originalVariables;
if (forImplementation)
{
originalVariables = matchingOwner.ImplementationVariables;
}
else
{
originalVariables = matchingOwner.RuntimeVariables;
}
int oldIndex = -1;
if (String.IsNullOrEmpty(newVariable.Name))
{
if (forImplementation)
{
// HasPrivateMemberChanged must have detected any presence of nameless private variable in advance.
oldIndex = newVariable.Owner.ImplementationVariables.IndexOf(newVariable);
}
else
{
// only for those variables without names, we attempt to match by MapItem tag
DynamicUpdateMapItem matchInfo = this.matchInfoLookup(newVariable.Origin ?? newVariable);
if (matchInfo != null && matchInfo.IsVariableMapItem && matchingOwner.InternalId == matchInfo.OriginalVariableOwnerId)
{
// "matchingOwner.InternalId != matchInfo.OriginalVariableOwnerId" means the variable has been moved to a different owner,
// and it is treated as a new variable addition.
oldIndex = matchInfo.OriginalId;
}
}
}
else
{
// named variables are matched by their Name, Type and Modifiers
for (int i = 0; i < originalVariables.Count; i++)
{
if (ActivityComparer.SignatureEquals(newVariable, originalVariables[i]))
{
// match by sig----ure(Name, Type, Modifier) found
oldIndex = i;
break;
}
}
}
if (oldIndex >= 0 && oldIndex < originalVariables.Count)
{
this.newToOldMatches.Add(newVariable, oldIndex);
return oldIndex;
}
return EnvironmentUpdateMapEntry.NonExistent;
}
bool ParentsMatch(Activity currentActivity, Activity originalActivity)
{
if (currentActivity.Parent == null)
{
return originalActivity.Parent == null;
}
else
{
if (currentActivity.RelationshipToParent != originalActivity.RelationshipToParent ||
(currentActivity.HandlerOf != null && currentActivity.HandlerOf.ParentCollectionType != originalActivity.HandlerOf.ParentCollectionType))
{
return false;
}
if (currentActivity.Parent == currentActivity.MemberOf.Owner)
{
return originalActivity.Parent == this.OldIdSpace.Owner;
}
return originalActivity.Parent != null &&
GetMatch(currentActivity.Parent) == originalActivity.Parent;
}
}
}
internal class NestedIdSpaceFinalizer : IDefinitionMatcher
{
Finalizer finalizer;
DynamicUpdateMap userProvidedMap;
DynamicUpdateMap generatedMap;
Activity updatedActivity;
Activity originalActivity;
bool invalidMatchInCurrentActivity;
NestedIdSpaceFinalizer parent;
public NestedIdSpaceFinalizer(Finalizer finalizer, DynamicUpdateMap implementationMap, Activity updatedActivity, Activity originalActivity, NestedIdSpaceFinalizer parent)
{
this.finalizer = finalizer;
this.userProvidedMap = implementationMap;
this.updatedActivity = updatedActivity;
this.originalActivity = originalActivity;
this.parent = parent;
}
public void ValidateOrCreateImplementationMap(DynamicUpdateMapEntry mapEntry)
{
// check applicability of the provided implementation map
if (this.userProvidedMap != null)
{
IdSpace privateIdSpace = updatedActivity.ParentOf;
if (privateIdSpace == null)
{
this.finalizer.ThrowInvalidImplementationMapAssociation(updatedActivity);
}
if (!this.userProvidedMap.IsNoChanges && privateIdSpace.MemberCount != this.userProvidedMap.NewDefinitionMemberCount)
{
BlockUpdate(updatedActivity, UpdateBlockedReason.InvalidImplementationMap, mapEntry,
SR.InvalidImplementationMap(this.userProvidedMap.NewDefinitionMemberCount, privateIdSpace.MemberCount));
return;
}
}
// The only difference between updatedActivity and originalActivity is changes in the outer IdSpace.
// The implementation IdSpace should never change in response to outer IdSpace changes.
// The only exception is addition/removal/rearrangement of RuntimeArguments and their Expressions in the private IdSpace for the sake of supporting Receive Content Parameter change.
// If any argument change is detected and nothing else changed in the private IdSpace,
// HasPrivateMemberOtherThanArgumentsChanged will return FALSE as well as returning a generated implementation Map.
// Also, when userProvidedMap exists, we don't allow changes to arguments inside the private IdSpace
DynamicUpdateMap argumentChangesMap;
if (ActivityComparer.HasPrivateMemberOtherThanArgumentsChanged(this, updatedActivity, originalActivity, this.parent == null, out argumentChangesMap) ||
(argumentChangesMap != null && this.userProvidedMap != null))
{
// either of the following two must have occured here.
// A.
// members in the private IdSpace(or nested IdSpaces) must have changed.
// addition/removal/rearrangement of arguments and their expressions in the private IdSpace are not considered as change.
//
// B.
// addition/removal/rearrangement of arguments or their expressions in the private IdSpace occured and no other members in the private IdSpace(or nested IdSpaces) changed except their id shift.
// Due to the id shift caused by argument change, an implementation map("argumentChangesMap") was created.
// In addition to "argumentChangesMap", there is also a user provided map. This blocks DU.
// generate a warning and block update inside this activity
BlockUpdate(updatedActivity, UpdateBlockedReason.PrivateMembersHaveChanged, mapEntry);
return;
}
if (updatedActivity.ParentOf != null)
{
GenerateMap(argumentChangesMap);
if (this.generatedMap == null)
{
mapEntry.ImplementationUpdateMap = this.userProvidedMap;
}
else
{
if (this.userProvidedMap == null || this.userProvidedMap.IsNoChanges)
{
FillGeneratedMap();
}
else
{
MergeProvidedMapIntoGeneratedMap();
}
mapEntry.ImplementationUpdateMap = this.generatedMap;
}
}
}
// AddMatch is a no-op, since any matches come from the provided implementation map.
// However an invalid match can still cause us to disallow update
public void AddMatch(Activity newChild, Activity oldChild, Activity source)
{
if (newChild.Parent != source || newChild.MemberOf != source.MemberOf || GetMatch(newChild) != oldChild)
{
this.invalidMatchInCurrentActivity = true;
}
}
public void AddMatch(Variable newVariable, Variable oldVariable, Activity source)
{
if (newVariable.Owner != source || !newVariable.IsPublic || GetMatch(newVariable) != oldVariable)
{
this.invalidMatchInCurrentActivity = true;
}
}
public Activity GetMatch(Activity newActivity)
{
NestedIdSpaceFinalizer owningFinalizer = this;
do
{
// The original definition being updated still needs to reference the updated implementation.
// So even if we have a provided impl map, there should be no ID changes between updatedActivity and original activity.
if (newActivity.MemberOf == owningFinalizer.updatedActivity.ParentOf)
{
return owningFinalizer.originalActivity.ParentOf[newActivity.InternalId];
}
owningFinalizer = owningFinalizer.parent;
}
while (owningFinalizer != null);
return this.finalizer.Matcher.GetMatch(newActivity);
}
public Variable GetMatch(Variable newVariable)
{
Fx.Assert(newVariable.Owner.MemberOf == this.updatedActivity.ParentOf, "Should only call GetMatch for variables owned by the participating activity");
int index = newVariable.Owner.RuntimeVariables.IndexOf(newVariable);
if (index >= 0)
{
Activity matchingOwner = GetMatch(newVariable.Owner);
if (matchingOwner != null && matchingOwner.RuntimeVariables.Count > index)
{
return matchingOwner.RuntimeVariables[index];
}
}
return null;
}
public void CreateArgumentEntries(DynamicUpdateMapEntry mapEntry, IList<RuntimeArgument> newArguments, IList<ArgumentInfo> oldArguments)
{
RuntimeArgument newIdleArgument;
Activity idleActivity;
if (!DynamicUpdateMapBuilder.Finalizer.CreateArgumentEntries(mapEntry, newArguments, oldArguments, this.finalizer.ExpressionRootsThatCanInduceIdle, out newIdleArgument, out idleActivity))
{
// If an argument expression goes idle, the activity it is declared on can potentially
// resume execution before the argument is evaluated. We can't allow that.
this.BlockUpdate(newIdleArgument.Owner, UpdateBlockedReason.AddedIdleExpression, mapEntry,
SR.AddedIdleArgumentBlockDU(newIdleArgument.Name, idleActivity));
return;
}
}
void BlockUpdate(Activity updatedActivity, UpdateBlockedReason reason, DynamicUpdateMapEntry entry, string message = null)
{
Activity originalActivity = GetMatch(updatedActivity);
Fx.Assert(originalActivity != null, "Cannot block update inside an added activity");
this.finalizer.BlockUpdate(updatedActivity, originalActivity.Id, reason, entry, message);
}
// This method allows activities in the implementation IdSpace to participate in map creation.
// This is necessary because they may need to save original values for properties whose value
// may be set by referencing activity properties. (E.g. <Receive OperationName='{PropertyReference OpName}' />)
// They may also disable update based on observed property values. However they are not allowed
// to change or add any matches, because the implementation IdSpace should not be changing
// based on public property changes.
// if argumentChangesMap is non-null, it will be used as the initial generatedMap onto which original values are saved.
void GenerateMap(DynamicUpdateMap argumentChangesMap)
{
IdSpace updatedIdSpace = this.updatedActivity.ParentOf;
IdSpace originalIdSpace = this.originalActivity.ParentOf;
for (int i = 1; i <= updatedIdSpace.MemberCount; i++)
{
DynamicUpdateMapEntry providedEntry = null;
if (this.userProvidedMap != null && !this.userProvidedMap.IsNoChanges)
{
bool isNewlyAdded = !this.userProvidedMap.TryGetUpdateEntryByNewId(i, out providedEntry);
if (isNewlyAdded || providedEntry.IsRuntimeUpdateBlocked ||
providedEntry.IsUpdateBlockedByUpdateAuthor || providedEntry.IsParentRemovedOrBlocked)
{
// No need to save original values or block update
continue;
}
}
DynamicUpdateMapEntry argumentChangesMapEntry = null;
if (argumentChangesMap != null)
{
Fx.Assert(!argumentChangesMap.IsNoChanges, "argumentChangesMap will never be NoChanges map because it is automatically created only when there is argument changes.");
bool isNewlyAdded = !argumentChangesMap.TryGetUpdateEntryByNewId(i, out argumentChangesMapEntry);
if (isNewlyAdded)
{
// No need to save original values or block update
continue;
}
}
// We only need to save this map entry if it has some non-default value.
DynamicUpdateMapEntry generatedEntry = GenerateEntry(argumentChangesMapEntry, providedEntry, i);
DynamicUpdateMap providedImplementationMap = providedEntry != null ? providedEntry.ImplementationUpdateMap : null;
if (generatedEntry.IsRuntimeUpdateBlocked ||
generatedEntry.SavedOriginalValues != null ||
generatedEntry.SavedOriginalValueFromParent != null ||
generatedEntry.ImplementationUpdateMap != providedImplementationMap ||
generatedEntry.IsIdChange ||
generatedEntry.HasEnvironmentUpdates)
{
EnsureGeneratedMap();
this.generatedMap.AddEntry(generatedEntry);
}
}
if (argumentChangesMap != null && argumentChangesMap.entries != null)
{
// add all IsRemoved entries
foreach (DynamicUpdateMapEntry entry in argumentChangesMap.entries)
{
if (entry.IsRemoval)
{
EnsureGeneratedMap();
this.generatedMap.AddEntry(entry);
}
}
}
}
void EnsureGeneratedMap()
{
if (this.generatedMap == null)
{
this.generatedMap = new DynamicUpdateMap
{
IsForImplementation = true,
NewDefinitionMemberCount = this.updatedActivity.ParentOf.MemberCount
};
}
}
DynamicUpdateMapEntry GenerateEntry(DynamicUpdateMapEntry argumentChangesMapEntry, DynamicUpdateMapEntry providedEntry, int id)
{
DynamicUpdateMapEntry generatedEntry;
Activity updatedChild;
Activity originalChild;
// argumentChangesMapEntry and providedEntry are mutually exclusive.
// both cannot be non-null at the same time although both may be null at the same time.
if (argumentChangesMapEntry == null)
{
int originalIndex = providedEntry != null ? providedEntry.OldActivityId : id;
generatedEntry = new DynamicUpdateMapEntry(originalIndex, id);
// we assume nothing has changed in the private IdSpace
updatedChild = this.updatedActivity.ParentOf[id];
originalChild = this.originalActivity.ParentOf[id];
}
else
{
generatedEntry = argumentChangesMapEntry;
// activity IDs in the private IdSpace has changed due to arguments change inside the private IdSpace
updatedChild = this.updatedActivity.ParentOf[argumentChangesMapEntry.NewActivityId];
originalChild = this.originalActivity.ParentOf[argumentChangesMapEntry.OldActivityId];
}
// Allow the activity to participate
this.invalidMatchInCurrentActivity = false;
this.finalizer.OnCreateDynamicUpdateMap(updatedChild, originalChild, generatedEntry, this);
if (this.invalidMatchInCurrentActivity && !generatedEntry.IsRuntimeUpdateBlocked)
{
BlockUpdate(updatedChild, UpdateBlockedReason.ChangeMatchesInImplementation, generatedEntry);
}
// Fill in the rest of the map entry;
generatedEntry.SavedOriginalValueFromParent = this.finalizer.GetSavedOriginalValueFromParent(updatedChild);
DynamicUpdateMap childImplementationMap = providedEntry != null ? providedEntry.ImplementationUpdateMap : null;
if (!generatedEntry.IsRuntimeUpdateBlocked)
{
NestedIdSpaceFinalizer nestedFinalizer = new NestedIdSpaceFinalizer(this.finalizer, childImplementationMap, updatedChild, originalChild, this);
nestedFinalizer.ValidateOrCreateImplementationMap(generatedEntry);
}
return generatedEntry;
}
// The generated map only contains entries that have some non-default value. For it to be a valid
// implementation map, we need to fill in all the unchanged entries.
void FillGeneratedMap()
{
Fx.Assert(this.generatedMap != null, "If there were no generated entries then we don't need a generated map.");
this.generatedMap.ArgumentsAreUnknown = true;
for (int i = 1; i <= this.originalActivity.ParentOf.MemberCount; i++)
{
DynamicUpdateMapEntry entry;
if (!this.generatedMap.TryGetUpdateEntry(i, out entry))
{
entry = new DynamicUpdateMapEntry(i, i);
this.generatedMap.AddEntry(entry);
}
entry.Parent = GetParentEntry(this.originalActivity.ParentOf[i], this.generatedMap);
}
}
void MergeProvidedMapIntoGeneratedMap()
{
this.generatedMap.OldArguments = this.userProvidedMap.OldArguments;
this.generatedMap.NewArguments = this.userProvidedMap.NewArguments;
for (int i = 1; i <= this.userProvidedMap.OldDefinitionMemberCount; i++)
{
// Get/create the matching generated entry
DynamicUpdateMapEntry providedEntry;
this.userProvidedMap.TryGetUpdateEntry(i, out providedEntry);
DynamicUpdateMapEntry generatedEntry = GetOrCreateGeneratedEntry(providedEntry);
if (generatedEntry.IsRemoval || generatedEntry.IsRuntimeUpdateBlocked || generatedEntry.IsUpdateBlockedByUpdateAuthor || generatedEntry.IsParentRemovedOrBlocked)
{
continue;
}
// Disable update if there's a conflict
int newActivityId = providedEntry.NewActivityId;
if (HasOverlap(providedEntry.SavedOriginalValues, generatedEntry.SavedOriginalValues) ||
(HasSavedOriginalValuesForChildren(newActivityId, this.userProvidedMap) && HasSavedOriginalValuesForChildren(newActivityId, this.generatedMap)))
{
Activity updatedChild = this.updatedActivity.ParentOf[generatedEntry.NewActivityId];
BlockUpdate(updatedChild, UpdateBlockedReason.GeneratedAndProvidedMapConflict, generatedEntry, SR.GeneratedAndProvidedMapConflict);
}
else
{
generatedEntry.SavedOriginalValues = DynamicUpdateMapEntry.Merge(generatedEntry.SavedOriginalValues, providedEntry.SavedOriginalValues);
}
}
}
DynamicUpdateMapEntry GetOrCreateGeneratedEntry(DynamicUpdateMapEntry providedEntry)
{
// Get or create the matching entry
DynamicUpdateMapEntry generatedEntry;
if (!this.generatedMap.TryGetUpdateEntry(providedEntry.OldActivityId, out generatedEntry))
{
generatedEntry = new DynamicUpdateMapEntry(providedEntry.OldActivityId, providedEntry.NewActivityId)
{
DisplayName = providedEntry.DisplayName,
BlockReason = providedEntry.BlockReason,
BlockReasonMessage = providedEntry.BlockReasonMessage,
IsUpdateBlockedByUpdateAuthor = providedEntry.IsUpdateBlockedByUpdateAuthor,
};
this.generatedMap.AddEntry(generatedEntry);
}
else
{
Fx.Assert(providedEntry.NewActivityId == generatedEntry.NewActivityId &&
providedEntry.DisplayName == generatedEntry.DisplayName &&
!providedEntry.IsRuntimeUpdateBlocked && !providedEntry.IsUpdateBlockedByUpdateAuthor,
"GeneratedEntry should be created with correct ID, and should not be created for an entry that has blocked update");
}
// Copy/fill additional values
generatedEntry.EnvironmentUpdateMap = providedEntry.EnvironmentUpdateMap;
if (providedEntry.Parent != null)
{
DynamicUpdateMapEntry parentEntry;
this.generatedMap.TryGetUpdateEntry(providedEntry.Parent.OldActivityId, out parentEntry);
Fx.Assert(parentEntry != null, "We process in IdSpace order, so we always process parents before their children");
generatedEntry.Parent = parentEntry;
}
if (generatedEntry.SavedOriginalValueFromParent == null)
{
generatedEntry.SavedOriginalValueFromParent = providedEntry.SavedOriginalValueFromParent;
}
if (generatedEntry.ImplementationUpdateMap == null)
{
generatedEntry.ImplementationUpdateMap = providedEntry.ImplementationUpdateMap;
}
return generatedEntry;
}
bool HasOverlap(IDictionary<string, object> providedValues, IDictionary<string, object> generatedValues)
{
return providedValues != null && generatedValues != null &&
providedValues.Keys.Any(k => generatedValues.ContainsKey(k));
}
bool HasSavedOriginalValuesForChildren(int parentNewActivityId, DynamicUpdateMap map)
{
foreach (Activity child in GetPublicDeclaredChildren(this.updatedActivity.ParentOf[parentNewActivityId], false))
{
DynamicUpdateMapEntry childEntry;
if (map.TryGetUpdateEntryByNewId(child.InternalId, out childEntry) &&
childEntry.SavedOriginalValueFromParent != null)
{
return true;
}
}
return false;
}
}
}
}
|