637 lines
24 KiB
C#
637 lines
24 KiB
C#
|
// <copyright>
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
|
||
|
namespace System.Activities.DynamicUpdate
|
||
|
{
|
||
|
using System;
|
||
|
using System.Activities.DynamicUpdate;
|
||
|
using System.Activities.XamlIntegration;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.ObjectModel;
|
||
|
using System.ComponentModel;
|
||
|
using System.Globalization;
|
||
|
using System.Runtime;
|
||
|
using System.Runtime.Serialization;
|
||
|
|
||
|
[DataContract]
|
||
|
[TypeConverter(typeof(DynamicUpdateMapConverter))]
|
||
|
public class DynamicUpdateMap
|
||
|
{
|
||
|
static DynamicUpdateMap noChanges = new DynamicUpdateMap();
|
||
|
static DynamicUpdateMap dummyMap = new DynamicUpdateMap();
|
||
|
|
||
|
internal EntryCollection entries;
|
||
|
IList<ArgumentInfo> newArguments;
|
||
|
IList<ArgumentInfo> oldArguments;
|
||
|
|
||
|
internal DynamicUpdateMap()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public static DynamicUpdateMap NoChanges
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return noChanges;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[DataMember(EmitDefaultValue = false, Name = "entries")]
|
||
|
internal EntryCollection SerializedEntries
|
||
|
{
|
||
|
get { return this.entries; }
|
||
|
set { this.entries = value; }
|
||
|
}
|
||
|
|
||
|
[DataMember(EmitDefaultValue = false, Name = "newArguments")]
|
||
|
internal IList<ArgumentInfo> SerializedNewArguments
|
||
|
{
|
||
|
get { return this.newArguments; }
|
||
|
set { this.newArguments = value; }
|
||
|
}
|
||
|
|
||
|
[DataMember(EmitDefaultValue = false, Name = "oldArguments")]
|
||
|
internal IList<ArgumentInfo> SerializedOldArguments
|
||
|
{
|
||
|
get { return this.oldArguments; }
|
||
|
set { this.oldArguments = value; }
|
||
|
}
|
||
|
|
||
|
// this is a dummy map to be used for creating a NativeActivityUpdateContext
|
||
|
// for calling UpdateInstance() on activities without map entries.
|
||
|
// this should not be used anywhere except for creating NativeActivityUpdateContext.
|
||
|
internal static DynamicUpdateMap DummyMap
|
||
|
{
|
||
|
get { return dummyMap; }
|
||
|
}
|
||
|
|
||
|
internal IList<ArgumentInfo> NewArguments
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.newArguments == null)
|
||
|
{
|
||
|
this.newArguments = new List<ArgumentInfo>();
|
||
|
}
|
||
|
return this.newArguments;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.newArguments = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal IList<ArgumentInfo> OldArguments
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.oldArguments == null)
|
||
|
{
|
||
|
this.oldArguments = new List<ArgumentInfo>();
|
||
|
}
|
||
|
return this.oldArguments;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.oldArguments = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[DataMember(EmitDefaultValue = false)]
|
||
|
internal bool ArgumentsAreUnknown
|
||
|
{
|
||
|
get;
|
||
|
set;
|
||
|
}
|
||
|
|
||
|
[DataMember(EmitDefaultValue = false)]
|
||
|
internal bool IsImplementationAsRoot
|
||
|
{
|
||
|
get;
|
||
|
set;
|
||
|
}
|
||
|
|
||
|
[DataMember(EmitDefaultValue = false)]
|
||
|
internal int NewDefinitionMemberCount
|
||
|
{
|
||
|
get;
|
||
|
set;
|
||
|
}
|
||
|
|
||
|
internal int OldDefinitionMemberCount
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.Entries.Count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[DataMember(EmitDefaultValue = false)]
|
||
|
internal bool IsForImplementation { get; set; }
|
||
|
|
||
|
// IdSpaces always have at least one member. So a count of 0 means that this is
|
||
|
// DynamicUpdateMap.NoChanges, or a serialized equivalent.
|
||
|
internal bool IsNoChanges
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.NewDefinitionMemberCount == 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// use the internal method AddEntry() instead
|
||
|
private IList<DynamicUpdateMapEntry> Entries
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.entries == null)
|
||
|
{
|
||
|
this.entries = new EntryCollection();
|
||
|
}
|
||
|
|
||
|
return this.entries;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static IDictionary<object, DynamicUpdateMapItem> CalculateMapItems(Activity workflowDefinitionToBeUpdated)
|
||
|
{
|
||
|
return CalculateMapItems(workflowDefinitionToBeUpdated, null);
|
||
|
}
|
||
|
|
||
|
public static IDictionary<object, DynamicUpdateMapItem> CalculateMapItems(Activity workflowDefinitionToBeUpdated, LocationReferenceEnvironment environment)
|
||
|
{
|
||
|
return InternalCalculateMapItems(workflowDefinitionToBeUpdated, environment, false);
|
||
|
}
|
||
|
|
||
|
public static IDictionary<object, DynamicUpdateMapItem> CalculateImplementationMapItems(Activity activityDefinitionToBeUpdated)
|
||
|
{
|
||
|
return CalculateImplementationMapItems(activityDefinitionToBeUpdated, null);
|
||
|
}
|
||
|
|
||
|
public static IDictionary<object, DynamicUpdateMapItem> CalculateImplementationMapItems(Activity activityDefinitionToBeUpdated, LocationReferenceEnvironment environment)
|
||
|
{
|
||
|
return InternalCalculateMapItems(activityDefinitionToBeUpdated, environment, true);
|
||
|
}
|
||
|
|
||
|
public static DynamicUpdateMap Merge(params DynamicUpdateMap[] maps)
|
||
|
{
|
||
|
return Merge((IEnumerable<DynamicUpdateMap>)maps);
|
||
|
}
|
||
|
|
||
|
public static DynamicUpdateMap Merge(IEnumerable<DynamicUpdateMap> maps)
|
||
|
{
|
||
|
if (maps == null)
|
||
|
{
|
||
|
throw FxTrace.Exception.ArgumentNull("maps");
|
||
|
}
|
||
|
|
||
|
// We could try to optimize this by merging the entire set at once, but it's simpler
|
||
|
// to just do pairwise merging
|
||
|
int index = 0;
|
||
|
DynamicUpdateMap result = null;
|
||
|
foreach (DynamicUpdateMap nextMap in maps)
|
||
|
{
|
||
|
result = Merge(result, nextMap, new MergeErrorContext { MapIndex = index });
|
||
|
index++;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static IDictionary<object, DynamicUpdateMapItem> InternalCalculateMapItems(Activity workflowDefinitionToBeUpdated, LocationReferenceEnvironment environment, bool forImplementation)
|
||
|
{
|
||
|
if (workflowDefinitionToBeUpdated == null)
|
||
|
{
|
||
|
throw FxTrace.Exception.ArgumentNull("workflowDefinitionToBeUpdated");
|
||
|
}
|
||
|
|
||
|
DynamicUpdateMapBuilder.Preparer preparer = new DynamicUpdateMapBuilder.Preparer(workflowDefinitionToBeUpdated, environment, forImplementation);
|
||
|
return preparer.Prepare();
|
||
|
}
|
||
|
|
||
|
public DynamicUpdateMapQuery Query(Activity updatedWorkflowDefinition, Activity originalWorkflowDefinition)
|
||
|
{
|
||
|
if (this.IsNoChanges)
|
||
|
{
|
||
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.NoChangesMapQueryNotSupported));
|
||
|
}
|
||
|
|
||
|
if (this.IsForImplementation)
|
||
|
{
|
||
|
ValidateDefinitionMatchesImplementationMap(updatedWorkflowDefinition, this.NewDefinitionMemberCount, "updatedWorkflowDefinition");
|
||
|
ValidateDefinitionMatchesImplementationMap(originalWorkflowDefinition, this.OldDefinitionMemberCount, "originalWorkflowDefinition");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ValidateDefinitionMatchesMap(updatedWorkflowDefinition, this.NewDefinitionMemberCount, "updatedWorkflowDefinition");
|
||
|
ValidateDefinitionMatchesMap(originalWorkflowDefinition, this.OldDefinitionMemberCount, "originalWorkflowDefinition");
|
||
|
}
|
||
|
|
||
|
return new DynamicUpdateMapQuery(this, updatedWorkflowDefinition, originalWorkflowDefinition);
|
||
|
}
|
||
|
|
||
|
internal static bool CanUseImplementationMapAsRoot(Activity workflowDefinition)
|
||
|
{
|
||
|
Fx.Assert(workflowDefinition.IsMetadataCached, "This should only be called for cached definition");
|
||
|
|
||
|
// We can only use the implementation map as a root map if the worklflow has no public children
|
||
|
return
|
||
|
workflowDefinition.Children.Count == 0 &&
|
||
|
workflowDefinition.ImportedChildren.Count == 0 &&
|
||
|
workflowDefinition.Delegates.Count == 0 &&
|
||
|
workflowDefinition.ImportedDelegates.Count == 0 &&
|
||
|
workflowDefinition.RuntimeVariables.Count == 0;
|
||
|
}
|
||
|
|
||
|
internal static DynamicUpdateMap Merge(DynamicUpdateMap first, DynamicUpdateMap second, MergeErrorContext errorContext)
|
||
|
{
|
||
|
if (first == null || second == null)
|
||
|
{
|
||
|
return first ?? second;
|
||
|
}
|
||
|
|
||
|
if (first.IsNoChanges || second.IsNoChanges)
|
||
|
{
|
||
|
// DynamicUpdateMap.NoChanges has zero members, so we need to special-case it here.
|
||
|
return first.IsNoChanges ? second : first;
|
||
|
}
|
||
|
|
||
|
ThrowIfMapsIncompatible(first, second, errorContext);
|
||
|
|
||
|
DynamicUpdateMap result = new DynamicUpdateMap
|
||
|
{
|
||
|
IsForImplementation = first.IsForImplementation,
|
||
|
NewDefinitionMemberCount = second.NewDefinitionMemberCount,
|
||
|
ArgumentsAreUnknown = first.ArgumentsAreUnknown && second.ArgumentsAreUnknown,
|
||
|
oldArguments = first.ArgumentsAreUnknown ? second.oldArguments : first.oldArguments,
|
||
|
newArguments = second.ArgumentsAreUnknown ? first.newArguments : second.newArguments
|
||
|
};
|
||
|
|
||
|
foreach (DynamicUpdateMapEntry firstEntry in first.Entries)
|
||
|
{
|
||
|
DynamicUpdateMapEntry parent = null;
|
||
|
if (firstEntry.Parent != null)
|
||
|
{
|
||
|
result.TryGetUpdateEntry(firstEntry.Parent.OldActivityId, out parent);
|
||
|
}
|
||
|
|
||
|
if (firstEntry.IsRemoval)
|
||
|
{
|
||
|
result.AddEntry(firstEntry.Clone(parent));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DynamicUpdateMapEntry secondEntry = second.entries[firstEntry.NewActivityId];
|
||
|
result.AddEntry(DynamicUpdateMapEntry.Merge(firstEntry, secondEntry, parent, errorContext));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
internal void AddEntry(DynamicUpdateMapEntry entry)
|
||
|
{
|
||
|
this.Entries.Add(entry);
|
||
|
}
|
||
|
|
||
|
// Wrap an implementation map in a dummy map. This allows use of an implementation map as the
|
||
|
// root map in the case when the root is an x:Class with no public children.
|
||
|
internal DynamicUpdateMap AsRootMap()
|
||
|
{
|
||
|
Fx.Assert(this.IsForImplementation, "This should only be called on implementation map");
|
||
|
|
||
|
if (!ActivityComparer.ListEquals(this.NewArguments, this.OldArguments))
|
||
|
{
|
||
|
throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidImplementationAsWorkflowRootForRuntimeStateBecauseArgumentsChanged));
|
||
|
}
|
||
|
|
||
|
DynamicUpdateMap result = new DynamicUpdateMap
|
||
|
{
|
||
|
IsImplementationAsRoot = true,
|
||
|
NewDefinitionMemberCount = 1
|
||
|
};
|
||
|
result.AddEntry(new DynamicUpdateMapEntry(1, 1)
|
||
|
{
|
||
|
ImplementationUpdateMap = this,
|
||
|
});
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
internal void ThrowIfInvalid(Activity updatedDefinition)
|
||
|
{
|
||
|
Fx.Assert(updatedDefinition.IsMetadataCached, "Caller should have ensured cached definition");
|
||
|
Fx.Assert(updatedDefinition.Parent == null && !this.IsForImplementation, "This should only be called on a workflow definition");
|
||
|
|
||
|
this.ThrowIfInvalid(updatedDefinition.MemberOf);
|
||
|
}
|
||
|
|
||
|
// We verify that the count of all IdSpaces is as expected.
|
||
|
// We could choose to be looser, and only check the IdSpaces that have children active;
|
||
|
// but realistically, if all provided implementation maps don't match, something is probably wrong.
|
||
|
// Conversely, we could check the correctness of every environment map, but it doesn't seem worth
|
||
|
// doing that much work. If we find a mismatch on the environment of an executing activity, we'll
|
||
|
// throw at that point.
|
||
|
void ThrowIfInvalid(IdSpace updatedIdSpace)
|
||
|
{
|
||
|
if (this.IsNoChanges)
|
||
|
{
|
||
|
// 0 means this is NoChanges map, since every workflow has at least one member
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.NewDefinitionMemberCount != updatedIdSpace.MemberCount)
|
||
|
{
|
||
|
throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidUpdateMap(
|
||
|
SR.WrongMemberCount(updatedIdSpace.Owner, updatedIdSpace.MemberCount, this.NewDefinitionMemberCount))));
|
||
|
}
|
||
|
|
||
|
foreach (DynamicUpdateMapEntry entry in this.Entries)
|
||
|
{
|
||
|
if (entry.ImplementationUpdateMap != null)
|
||
|
{
|
||
|
Activity implementationOwner = updatedIdSpace[entry.NewActivityId];
|
||
|
if (implementationOwner == null)
|
||
|
{
|
||
|
string expectedId = entry.NewActivityId.ToString(CultureInfo.InvariantCulture);
|
||
|
if (updatedIdSpace.Owner != null)
|
||
|
{
|
||
|
expectedId = updatedIdSpace.Owner.Id + "." + expectedId;
|
||
|
}
|
||
|
throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidUpdateMap(
|
||
|
SR.ActivityNotFound(expectedId))));
|
||
|
}
|
||
|
|
||
|
if (implementationOwner.ParentOf == null)
|
||
|
{
|
||
|
throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidUpdateMap(
|
||
|
SR.ActivityHasNoImplementation(implementationOwner))));
|
||
|
}
|
||
|
|
||
|
entry.ImplementationUpdateMap.ThrowIfInvalid(implementationOwner.ParentOf);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal bool TryGetUpdateEntryByNewId(int newId, out DynamicUpdateMapEntry entry)
|
||
|
{
|
||
|
Fx.Assert(!this.IsNoChanges, "This method is never supposed to be called on the NoChanges map.");
|
||
|
|
||
|
entry = null;
|
||
|
|
||
|
for (int i = 0; i < this.Entries.Count; i++)
|
||
|
{
|
||
|
DynamicUpdateMapEntry currentEntry = this.Entries[i];
|
||
|
if (currentEntry.NewActivityId == newId)
|
||
|
{
|
||
|
entry = currentEntry;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
internal bool TryGetUpdateEntry(int oldId, out DynamicUpdateMapEntry entry)
|
||
|
{
|
||
|
if (this.entries != null && this.entries.Count > 0)
|
||
|
{
|
||
|
if (this.entries.Contains(oldId))
|
||
|
{
|
||
|
entry = this.entries[oldId];
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
entry = null;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// rootIdSpace is optional. if it's null, result.NewActivity will be null
|
||
|
internal UpdatedActivity GetUpdatedActivity(QualifiedId oldQualifiedId, IdSpace rootIdSpace)
|
||
|
{
|
||
|
UpdatedActivity result = new UpdatedActivity();
|
||
|
int[] oldIdSegments = oldQualifiedId.AsIDArray();
|
||
|
int[] newIdSegments = null;
|
||
|
IdSpace currentIdSpace = rootIdSpace;
|
||
|
DynamicUpdateMap currentMap = this;
|
||
|
|
||
|
Fx.Assert(!this.IsForImplementation, "This method is never supposed to be called on an implementation map.");
|
||
|
|
||
|
for (int i = 0; i < oldIdSegments.Length; i++)
|
||
|
{
|
||
|
if (currentMap == null || currentMap.Entries.Count == 0)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
DynamicUpdateMapEntry entry;
|
||
|
if (!currentMap.TryGetUpdateEntry(oldIdSegments[i], out entry))
|
||
|
{
|
||
|
// UpdateMap should contain entries for all old activities in the IdSpace
|
||
|
int[] subIdSegments = new int[i + 1];
|
||
|
Array.Copy(oldIdSegments, subIdSegments, subIdSegments.Length);
|
||
|
throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidUpdateMap(
|
||
|
SR.MapEntryNotFound(new QualifiedId(subIdSegments)))));
|
||
|
}
|
||
|
|
||
|
if (entry.IsIdChange)
|
||
|
{
|
||
|
if (newIdSegments == null)
|
||
|
{
|
||
|
newIdSegments = new int[oldIdSegments.Length];
|
||
|
Array.Copy(oldIdSegments, newIdSegments, oldIdSegments.Length);
|
||
|
}
|
||
|
|
||
|
newIdSegments[i] = entry.NewActivityId;
|
||
|
}
|
||
|
|
||
|
Activity currentActivity = null;
|
||
|
if (currentIdSpace != null && !entry.IsRemoval)
|
||
|
{
|
||
|
currentActivity = currentIdSpace[entry.NewActivityId];
|
||
|
if (currentActivity == null)
|
||
|
{
|
||
|
// New Activity pointed to by UpdateMap should exist
|
||
|
string activityId = currentIdSpace.Owner.Id + "." + entry.NewActivityId.ToString(CultureInfo.InvariantCulture);
|
||
|
throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidUpdateMap(
|
||
|
SR.ActivityNotFound(activityId))));
|
||
|
}
|
||
|
currentIdSpace = currentActivity.ParentOf;
|
||
|
}
|
||
|
|
||
|
if (i == oldIdSegments.Length - 1)
|
||
|
{
|
||
|
result.Map = currentMap;
|
||
|
result.MapEntry = entry;
|
||
|
result.NewActivity = currentActivity;
|
||
|
}
|
||
|
else if (entry.IsRuntimeUpdateBlocked || entry.IsUpdateBlockedByUpdateAuthor)
|
||
|
{
|
||
|
currentMap = null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
currentMap = entry.ImplementationUpdateMap;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
result.IdChanged = newIdSegments != null;
|
||
|
result.NewId = result.IdChanged ? new QualifiedId(newIdSegments) : oldQualifiedId;
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static void ThrowIfMapsIncompatible(DynamicUpdateMap first, DynamicUpdateMap second, MergeErrorContext errorContext)
|
||
|
{
|
||
|
Fx.Assert(!first.IsNoChanges && !second.IsNoChanges, "This method is never supposed to be called on the NoChanges map.");
|
||
|
|
||
|
if (first.IsForImplementation != second.IsForImplementation)
|
||
|
{
|
||
|
errorContext.Throw(SR.InvalidMergeMapForImplementation(first.IsForImplementation, second.IsForImplementation));
|
||
|
}
|
||
|
if (first.NewDefinitionMemberCount != second.OldDefinitionMemberCount)
|
||
|
{
|
||
|
errorContext.Throw(SR.InvalidMergeMapMemberCount(first.NewDefinitionMemberCount, second.OldDefinitionMemberCount));
|
||
|
}
|
||
|
if (!first.ArgumentsAreUnknown && !second.ArgumentsAreUnknown && first.IsForImplementation &&
|
||
|
!ActivityComparer.ListEquals(first.newArguments, second.oldArguments))
|
||
|
{
|
||
|
if (first.NewArguments.Count != second.OldArguments.Count)
|
||
|
{
|
||
|
errorContext.Throw(SR.InvalidMergeMapArgumentCount(first.NewArguments.Count, second.OldArguments.Count));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
errorContext.Throw(SR.InvalidMergeMapArgumentsChanged);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void ValidateDefinitionMatchesMap(Activity activity, int memberCount, string parameterName)
|
||
|
{
|
||
|
if (activity == null)
|
||
|
{
|
||
|
throw FxTrace.Exception.ArgumentNull(parameterName);
|
||
|
}
|
||
|
if (activity.MemberOf == null)
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument(parameterName, SR.ActivityIsUncached);
|
||
|
}
|
||
|
if (activity.Parent != null)
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument(parameterName, SR.ActivityIsNotRoot);
|
||
|
}
|
||
|
if (activity.MemberOf.MemberCount != memberCount)
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument(parameterName, SR.InvalidUpdateMap(
|
||
|
SR.WrongMemberCount(activity.MemberOf.Owner, activity.MemberOf.MemberCount, memberCount)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void ValidateDefinitionMatchesImplementationMap(Activity activity, int memberCount, string parameterName)
|
||
|
{
|
||
|
if (activity == null)
|
||
|
{
|
||
|
throw FxTrace.Exception.ArgumentNull(parameterName);
|
||
|
}
|
||
|
if (activity.MemberOf == null)
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument(parameterName, SR.ActivityIsUncached);
|
||
|
}
|
||
|
if (activity.Parent != null)
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument(parameterName, SR.ActivityIsNotRoot);
|
||
|
}
|
||
|
if (activity.ParentOf == null)
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument(parameterName, SR.InvalidUpdateMap(
|
||
|
SR.ActivityHasNoImplementation(activity)));
|
||
|
}
|
||
|
if (activity.ParentOf.MemberCount != memberCount)
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument(parameterName, SR.InvalidUpdateMap(
|
||
|
SR.WrongMemberCount(activity.ParentOf.Owner, activity.ParentOf.MemberCount, memberCount)));
|
||
|
}
|
||
|
if (!CanUseImplementationMapAsRoot(activity))
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument(parameterName, SR.InvalidImplementationAsWorkflowRoot);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal struct UpdatedActivity
|
||
|
{
|
||
|
// This can be true even if Map & MapEntry are null, if a parent ID changed.
|
||
|
// It can also be false even when Map & MapEntry are non-null, if the update didn't produce an ID shift.
|
||
|
public bool IdChanged;
|
||
|
|
||
|
public QualifiedId NewId;
|
||
|
|
||
|
// Null if the activity's IDSpace wasn't updated.
|
||
|
public DynamicUpdateMap Map;
|
||
|
public DynamicUpdateMapEntry MapEntry;
|
||
|
|
||
|
// Null when we're dealing with just a serialized instance with no definition.
|
||
|
public Activity NewActivity;
|
||
|
}
|
||
|
|
||
|
internal class MergeErrorContext
|
||
|
{
|
||
|
private Stack<int> currentIdSpace;
|
||
|
public int MapIndex { get; set; }
|
||
|
|
||
|
public void PushIdSpace(int id)
|
||
|
{
|
||
|
if (this.currentIdSpace == null)
|
||
|
{
|
||
|
this.currentIdSpace = new Stack<int>();
|
||
|
}
|
||
|
this.currentIdSpace.Push(id);
|
||
|
}
|
||
|
|
||
|
public void PopIdSpace()
|
||
|
{
|
||
|
this.currentIdSpace.Pop();
|
||
|
}
|
||
|
|
||
|
public void Throw(string detail)
|
||
|
{
|
||
|
QualifiedId id = null;
|
||
|
if (this.currentIdSpace != null && this.currentIdSpace.Count > 0)
|
||
|
{
|
||
|
int[] idSegments = new int[this.currentIdSpace.Count];
|
||
|
for (int i = idSegments.Length - 1; i >= 0; i--)
|
||
|
{
|
||
|
idSegments[i] = this.currentIdSpace.Pop();
|
||
|
}
|
||
|
id = new QualifiedId(idSegments);
|
||
|
}
|
||
|
|
||
|
string errorMessage;
|
||
|
if (id == null)
|
||
|
{
|
||
|
errorMessage = SR.InvalidRootMergeMap(this.MapIndex, detail);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
errorMessage = SR.InvalidMergeMap(this.MapIndex, id, detail);
|
||
|
}
|
||
|
|
||
|
throw FxTrace.Exception.Argument("maps", errorMessage);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[CollectionDataContract]
|
||
|
internal class EntryCollection : KeyedCollection<int, DynamicUpdateMapEntry>
|
||
|
{
|
||
|
public EntryCollection()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
protected override int GetKeyForItem(DynamicUpdateMapEntry item)
|
||
|
{
|
||
|
return item.OldActivityId;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|