1// <copyright>
2//   Copyright (c) Microsoft Corporation.  All rights reserved.
3// </copyright>
4
5namespace System.Activities.DynamicUpdate
6{
7    using System;
8    using System.Activities.DynamicUpdate;
9    using System.Activities.Validation;
10    using System.Collections;
11    using System.Collections.Generic;
12    using System.Diagnostics.CodeAnalysis;
13    using System.Globalization;
14    using System.Linq;
15    using System.Runtime;
16    using System.Runtime.CompilerServices;
17
18    public class DynamicUpdateMapBuilder
19    {
20        private HashSet<Activity> disallowUpdateInside;
21
22        public DynamicUpdateMapBuilder()
23        {
24        }
25
26        public bool ForImplementation
27        {
28            get;
29            set;
30        }
31
32        public ISet<Activity> DisallowUpdateInside
33        {
34            get
35            {
36                if (this.disallowUpdateInside == null)
37                {
38                    this.disallowUpdateInside = new HashSet<Activity>(ReferenceEqualityComparer.Instance);
39                }
40
41                return this.disallowUpdateInside;
42            }
43        }
44
45        public Func<object, DynamicUpdateMapItem> LookupMapItem
46        {
47            get;
48            set;
49        }
50
51        public Func<Activity, DynamicUpdateMap> LookupImplementationMap
52        {
53            get;
54            set;
55        }
56
57        public LocationReferenceEnvironment UpdatedEnvironment
58        {
59            get;
60            set;
61        }
62
63        public Activity UpdatedWorkflowDefinition
64        {
65            get;
66            set;
67        }
68
69        public LocationReferenceEnvironment OriginalEnvironment
70        {
71            get;
72            set;
73        }
74
75        public Activity OriginalWorkflowDefinition
76        {
77            get;
78            set;
79        }
80
81        // Internal hook to allow DynamicUpdateServices to surface a customized error message when
82        // there is an invalid activity in the disallowUpdateInsideActivities list
83        internal Func<Activity, Exception> OnInvalidActivityToBlockUpdate
84        {
85            get;
86            set;
87        }
88
89        // Internal hook to allow DynamicUpdateServices to surface a customized error message when
90        // there is an invalid activity in the disallowUpdateInsideActivities list
91        internal Func<Activity, Exception> OnInvalidImplementationMapAssociation
92        {
93            get;
94            set;
95        }
96
97        public DynamicUpdateMap CreateMap()
98        {
99            IList<ActivityBlockingUpdate> activitiesBlockingUpdate;
100            return CreateMap(out activitiesBlockingUpdate);
101        }
102
103        [SuppressMessage(FxCop.Category.Design, FxCop.Rule.AvoidOutParameters, Justification = "Approved Design. Need to return the map and the block list.")]
104        public DynamicUpdateMap CreateMap(out IList<ActivityBlockingUpdate> activitiesBlockingUpdate)
105        {
106            RequireProperty(this.LookupMapItem, "LookupMapItem");
107            RequireProperty(this.UpdatedWorkflowDefinition, "UpdatedWorkflowDefinition");
108            RequireProperty(this.OriginalWorkflowDefinition, "OriginalWorkflowDefinition");
109
110            Finalizer finalizer = new Finalizer(this);
111            DynamicUpdateMap result = finalizer.FinalizeUpdate(out activitiesBlockingUpdate);
112            return result;
113        }
114
115        private static void CacheMetadata(Activity workflowDefinition, LocationReferenceEnvironment environment, ActivityUtilities.ProcessActivityCallback callback, bool forImplementation)
116        {
117            IList<ValidationError> validationErrors = null;
118            ActivityUtilities.CacheRootMetadata(workflowDefinition, environment, ProcessTreeOptions(forImplementation), callback, ref validationErrors);
119            ActivityValidationServices.ThrowIfViolationsExist(validationErrors);
120        }
121
122        static DynamicUpdateMapEntry GetParentEntry(Activity originalActivity, DynamicUpdateMap updateMap)
123        {
124            if (originalActivity.Parent != null && originalActivity.Parent.MemberOf == originalActivity.MemberOf)
125            {
126                DynamicUpdateMapEntry parentEntry;
127                updateMap.TryGetUpdateEntry(originalActivity.Parent.InternalId, out parentEntry);
128                Fx.Assert(parentEntry != null, "We process in IdSpace order, so we always process parents before their children");
129                return parentEntry;
130            }
131            return null;
132        }
133
134        static IEnumerable<Activity> GetPublicDeclaredChildren(Activity activity, bool includeExpressions)
135        {
136            IEnumerable<Activity> result = activity.Children.Concat(
137                activity.ImportedChildren).Concat(
138                activity.Delegates.Select(d => d.Handler)).Concat(
139                activity.ImportedDelegates.Select(d => d.Handler));
140            if (includeExpressions)
141            {
142                result = result.Concat(
143                    activity.RuntimeVariables.Select(v => v.Default)).Concat(
144                    activity.RuntimeArguments.Select(a => a.IsBound ? a.BoundArgument.Expression : null));
145            }
146
147            return result.Where(a => a != null && a.Parent == activity);
148        }
149
150        private static ProcessActivityTreeOptions ProcessTreeOptions(bool forImplementation)
151        {
152            return forImplementation ? ProcessActivityTreeOptions.DynamicUpdateOptionsForImplementation : ProcessActivityTreeOptions.DynamicUpdateOptions;
153        }
154
155        private static void RequireProperty(object value, string name)
156        {
157            if (value == null)
158            {
159                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.UpdateMapBuilderRequiredProperty(name)));
160            }
161        }
162
163        internal class ReferenceEqualityComparer : IEqualityComparer<object>
164        {
165            public static readonly IEqualityComparer<object> Instance = new ReferenceEqualityComparer();
166
167            ReferenceEqualityComparer()
168            {
169            }
170
171            public new bool Equals(object x, object y)
172            {
173                return object.ReferenceEquals(x, y);
174            }
175
176            public int GetHashCode(object obj)
177            {
178                return RuntimeHelpers.GetHashCode(obj);
179            }
180        }
181
182        // Preparer walks the tree and identifies, for each object in the tree, an ID that can be
183        // attached to an object in the new definition to match it to the equivalent object in the
184        // old definition.
185        internal class Preparer
186        {
187            private Dictionary<object, DynamicUpdateMapItem> updateableObjects;
188            private Activity originalProgram;
189            private LocationReferenceEnvironment originalEnvironment;
190            private bool forImplementation;
191
192            public Preparer(Activity originalProgram, LocationReferenceEnvironment originalEnvironment, bool forImplementation)
193            {
194                this.originalProgram = originalProgram;
195                this.originalEnvironment = originalEnvironment;
196                this.forImplementation = forImplementation;
197            }
198
199            public Dictionary<object, DynamicUpdateMapItem> Prepare()
200            {
201                this.updateableObjects = new Dictionary<object, DynamicUpdateMapItem>(ReferenceEqualityComparer.Instance);
202                CacheMetadata(this.originalProgram, this.originalEnvironment, null, this.forImplementation);
203
204                IdSpace idSpace = GetIdSpace();
205                if (idSpace != null)
206                {
207                    for (int i = 1; i <= idSpace.MemberCount; i++)
208                    {
209                        ProcessElement(idSpace[i]);
210                    }
211                }
212
213                return this.updateableObjects;
214            }
215
216            IdSpace GetIdSpace()
217            {
218                return this.forImplementation ? this.originalProgram.ParentOf : this.originalProgram.MemberOf;
219            }
220
221            void ProcessElement(Activity currentElement)
222            {
223                // Attach the original Activity ID to the activity
224                // The origin of a variable default is the same as the origin of the variable itself.
225                // So we don't attach match info for the default, since that would conflict with
226                // the match info for the variable.
227                if (currentElement.RelationshipToParent != Activity.RelationshipType.VariableDefault || currentElement.Origin == null)
228                {
229                    ValidateOrigin(currentElement.Origin, currentElement);
230                    this.updateableObjects[currentElement.Origin ?? currentElement] = new DynamicUpdateMapItem(currentElement.InternalId);
231                }
232
233                // Attach the original variable index to the variable
234                IList<Variable> variables = currentElement.RuntimeVariables;
235                for (int i = 0; i < variables.Count; i++)
236                {
237                    Variable variable = variables[i];
238                    if (string.IsNullOrEmpty(variable.Name))
239                    {
240                        ValidateOrigin(variable.Origin, variable);
241                        this.updateableObjects[variable.Origin ?? variable] = new DynamicUpdateMapItem(currentElement.InternalId, i);
242                    }
243                }
244            }
245
246            void ValidateOrigin(object origin, object element)
247            {
248                if (origin != null)
249                {
250                    DynamicUpdateMapItem mapItem;
251                    if (this.updateableObjects.TryGetValue(origin, out mapItem))
252                    {
253                        string error = null;
254                        if (mapItem.IsVariableMapItem)
255                        {
256                            Variable dupe = GetVariable(mapItem);
257                            Variable elementVar = element as Variable;
258                            if (elementVar != null)
259                            {
260                                error = SR.DuplicateOriginVariableVariable(origin, dupe.Name, elementVar.Name);
261                            }
262                            else
263                            {
264                                error = SR.DuplicateOriginActivityVariable(origin, element, dupe.Name);
265                            }
266                        }
267                        else
268                        {
269                            Activity dupe = GetActivity(mapItem);
270                            Variable elementVar = element as Variable;
271                            if (elementVar != null)
272                            {
273                                error = SR.DuplicateOriginActivityVariable(origin, dupe, elementVar.Name);
274                            }
275                            else
276                            {
277                                error = SR.DuplicateOriginActivityActivity(origin, dupe, element);
278                            }
279                        }
280
281                        throw FxTrace.Exception.AsError(new InvalidWorkflowException(error));
282                    }
283                }
284            }
285
286            Activity GetActivity(DynamicUpdateMapItem mapItem)
287            {
288                return GetIdSpace()[mapItem.OriginalId];
289            }
290
291            Variable GetVariable(DynamicUpdateMapItem mapItem)
292            {
293                return GetIdSpace()[mapItem.OriginalVariableOwnerId].RuntimeVariables[mapItem.OriginalId];
294            }
295        }
296
297        // Builds an Update Map given an old and new definition, and matches between them
298        internal class Finalizer
299        {
300            BitArray foundOriginalElements;
301            DynamicUpdateMapBuilder builder;
302            DynamicUpdateMap updateMap;
303            Dictionary<Activity, object> savedOriginalValues;
304            bool savedOriginalValuesForReferencedChildren;
305            IList<ActivityBlockingUpdate> blockList;
306            // dictionary from expression root to the activity that can make it go idle
307            Dictionary<Activity, Activity> expressionRootsThatCanInduceIdle;
308
309            public Finalizer(DynamicUpdateMapBuilder builder)
310            {
311                this.builder = builder;
312                this.savedOriginalValues = new Dictionary<Activity, object>(ReferenceEqualityComparer.Instance);
313                this.Matcher = new DefinitionMatcher(builder.LookupMapItem);
314            }
315
316            public DynamicUpdateMap FinalizeUpdate(out IList<ActivityBlockingUpdate> blockList)
317            {
318                this.updateMap = new DynamicUpdateMap();
319                this.blockList = new List<ActivityBlockingUpdate>();
320
321                // cache metadata of originalProgram
322                CacheMetadata(this.builder.OriginalWorkflowDefinition, this.builder.OriginalEnvironment, null, this.builder.ForImplementation);
323                IdSpace originalIdSpace = this.builder.ForImplementation ? this.builder.OriginalWorkflowDefinition.ParentOf : this.builder.OriginalWorkflowDefinition.MemberOf;
324                if (originalIdSpace == null)
325                {
326                    Fx.Assert(this.builder.ForImplementation, "An activity must be a member of an IdSpace");
327                    throw FxTrace.Exception.AsError(new InvalidOperationException(SR.InvalidOriginalWorkflowDefinitionForImplementationMapCreation));
328                }
329                this.Matcher.OldIdSpace = originalIdSpace;
330                this.foundOriginalElements = new BitArray(originalIdSpace.MemberCount);
331
332                // cache metadata of modifiedProgram before iterative ProcessElement()
333                CacheMetadata(this.builder.UpdatedWorkflowDefinition, this.builder.UpdatedEnvironment, CheckCanArgumentOrVariableDefaultInduceIdle, this.builder.ForImplementation);
334                IdSpace idSpace = this.builder.ForImplementation ? this.builder.UpdatedWorkflowDefinition.ParentOf : this.builder.UpdatedWorkflowDefinition.MemberOf;
335                if (idSpace == null)
336                {
337                    Fx.Assert(this.builder.ForImplementation, "An activity must be a member of an IdSpace");
338                    throw FxTrace.Exception.AsError(new InvalidOperationException(SR.InvalidUpdatedWorkflowDefinitionForImplementationMapCreation));
339                }
340                this.Matcher.NewIdSpace = idSpace;
341
342                // check if any of the activities or variables from the original definition
343                // were reused in the updated definition
344                for (int i = 1; i < originalIdSpace.MemberCount + 1; i++)
345                {
346                    CheckForReusedActivity(originalIdSpace[i]);
347                }
348
349                // most of the updatemap construction processing
350                for (int i = 1; i < idSpace.MemberCount + 1; i++)
351                {
352                    ProcessElement(idSpace[i]);
353                }
354
355                // if an activity doesn't have an entry by this point, that means it was removed
356                for (int i = 0; i < this.foundOriginalElements.Count; i++)
357                {
358                    if (!this.foundOriginalElements[i])
359                    {
360                        DynamicUpdateMapEntry removalEntry = new DynamicUpdateMapEntry(i + 1, 0);
361                        Activity originalActivity = originalIdSpace[i + 1];
362                        removalEntry.Parent = GetParentEntry(originalActivity, this.updateMap);
363                        if (!removalEntry.IsParentRemovedOrBlocked)
364                        {
365                            removalEntry.DisplayName = originalActivity.DisplayName;
366                        }
367                        this.updateMap.AddEntry(removalEntry);
368                    }
369                }
370
371                if (this.builder.ForImplementation)
372                {
373                    this.updateMap.IsForImplementation = true;
374
375                    // gather arguments diff between new and old activity definitions
376                    this.updateMap.OldArguments = ArgumentInfo.List(builder.OriginalWorkflowDefinition);
377                    this.updateMap.NewArguments = ArgumentInfo.List(builder.UpdatedWorkflowDefinition);
378                }
379
380                // Validate the Disallow entries
381                foreach (Activity disallowActivity in this.builder.DisallowUpdateInside)
382                {
383                    if (disallowActivity == null)
384                    {
385                        continue;
386                    }
387
388                    if (disallowActivity.MemberOf != idSpace)
389                    {
390                        ThrowInvalidActivityToBlockUpdate(disallowActivity);
391                    }
392                }
393
394                this.updateMap.NewDefinitionMemberCount = idSpace.MemberCount;
395                blockList = this.blockList;
396                return this.updateMap;
397            }
398
399            internal bool? AllowUpdateInsideCurrentActivity
400            {
401                get;
402                set;
403            }
404
405            internal string UpdateDisallowedReason
406            {
407                get;
408                set;
409            }
410
411            internal Dictionary<string, object> SavedOriginalValuesForCurrentActivity
412            {
413                get;
414                set;
415            }
416
417            internal DefinitionMatcher Matcher
418            {
419                get;
420                private set;
421            }
422
423            internal Dictionary<Activity, Activity> ExpressionRootsThatCanInduceIdle
424            {
425                get
426                {
427                    return this.expressionRootsThatCanInduceIdle;
428                }
429            }
430
431            void BlockUpdate(Activity activity, UpdateBlockedReason reason, DynamicUpdateMapEntry entry, string message = null)
432            {
433                Fx.Assert(activity.MemberOf == (this.builder.ForImplementation ? activity.RootActivity.ParentOf : activity.RootActivity.MemberOf), "Should have called other overload of BlockUpdate");
434                BlockUpdate(activity, entry.OldActivityId.ToString(CultureInfo.InvariantCulture), reason, entry, message);
435            }
436
437            internal void BlockUpdate(Activity activity, string originalActivityId, UpdateBlockedReason reason, DynamicUpdateMapEntry entry, string message = null)
438            {
439                Fx.Assert(reason != UpdateBlockedReason.NotBlocked, "Invalid block reason");
440                if (!entry.IsRuntimeUpdateBlocked)
441                {
442                    entry.BlockReason = reason;
443                    if (reason == UpdateBlockedReason.Custom)
444                    {
445                        entry.BlockReasonMessage = message;
446                    }
447                    entry.ImplementationUpdateMap = null;
448
449                    this.blockList.Add(new ActivityBlockingUpdate(activity, originalActivityId, message ?? UpdateBlockedReasonMessages.Get(reason)));
450                }
451            }
452
453            internal void SetOriginalValue(Activity key, object value, bool isReferencedChild)
454            {
455                if (isReferencedChild)
456                {
457                    this.savedOriginalValuesForReferencedChildren = true;
458                }
459                else
460                {
461                    this.savedOriginalValues[key] = value;
462                }
463            }
464
465            internal object GetSavedOriginalValueFromParent(Activity key)
466            {
467                object result = null;
468                this.savedOriginalValues.TryGetValue(key, out result);
469                return result;
470            }
471
472            void ProcessElement(Activity currentElement)
473            {
474                Activity originalElement = this.Matcher.GetMatch(currentElement);
475                if (originalElement != null)
476                {
477                    // this means it's an existing one
478
479                    DynamicUpdateMapEntry mapEntry = this.CreateMapEntry(currentElement, originalElement);
480                    mapEntry.Parent = GetParentEntry(originalElement, this.updateMap);
481                    if (this.builder.DisallowUpdateInside.Contains(currentElement))
482                    {
483                        mapEntry.IsUpdateBlockedByUpdateAuthor = true;
484                    }
485                    if (originalElement.GetType() != currentElement.GetType())
486                    {
487                        // returned matching activity's type doesn't really match the currentElement
488                        BlockUpdate(currentElement, UpdateBlockedReason.TypeChange, mapEntry,
489                            SR.DUActivityTypeMismatch(currentElement.GetType(), originalElement.GetType()));
490                    }
491                    if (this.DelegateArgumentsChanged(currentElement, originalElement))
492                    {
493                        this.BlockUpdate(currentElement, UpdateBlockedReason.DelegateArgumentChange, mapEntry);
494                    }
495
496                    DynamicUpdateMap implementationMap = null;
497                    if (this.builder.LookupImplementationMap != null)
498                    {
499                        implementationMap = this.builder.LookupImplementationMap(currentElement);
500                    }
501
502                    // fill ArgumentEntries
503                    // get arguments diff info from implementation map if it exists
504                    // we do this before user participation, so that we don't call into user code
505                    // if the update is invalid
506                    IList<ArgumentInfo> oldArguments = GetOriginalArguments(mapEntry, implementationMap, currentElement, originalElement);
507                    if (oldArguments != null)
508                    {
509                        CreateArgumentEntries(mapEntry, currentElement.RuntimeArguments, oldArguments);
510                    }
511
512                    // Capture any saved original value associated with this activity by its parent
513                    mapEntry.SavedOriginalValueFromParent = GetSavedOriginalValueFromParent(currentElement);
514
515                    if (mapEntry.IsRuntimeUpdateBlocked)
516                    {
517                        // don't allow activity to participate if update isn't possible anyway
518                        mapEntry.EnvironmentUpdateMap = null;
519                        return;
520                    }
521
522                    OnCreateDynamicUpdateMap(currentElement, originalElement, mapEntry, this.Matcher);
523                    if (mapEntry.IsRuntimeUpdateBlocked)
524                    {
525                        // if the activity disabled update, we can't rely on the variable matches,
526                        // so no point in proceeding
527                        mapEntry.EnvironmentUpdateMap = null;
528                        return;
529                    }
530
531                    // variable entries need to be calculated after activity participation, since
532                    // the activity can participate in matching them
533                    CreateVariableEntries(false, mapEntry, currentElement.RuntimeVariables, originalElement.RuntimeVariables, originalElement);
534                    CreateVariableEntries(true, mapEntry, currentElement.ImplementationVariables, originalElement.ImplementationVariables, originalElement);
535
536                    if (mapEntry.HasEnvironmentUpdates)
537                    {
538                        FillEnvironmentMapMemberCounts(mapEntry.EnvironmentUpdateMap, currentElement, originalElement, oldArguments);
539                    }
540                    else
541                    {
542                        Fx.Assert(originalElement.SymbolCount == currentElement.SymbolCount ||
543                            originalElement.ImplementationVariables.Count != currentElement.ImplementationVariables.Count,
544                            "Should have environment update if symbol count changed");
545                    }
546
547                    if (!mapEntry.IsParentRemovedOrBlocked && !mapEntry.IsUpdateBlockedByUpdateAuthor)
548                    {
549                        NestedIdSpaceFinalizer nestedFinalizer = new NestedIdSpaceFinalizer(this, implementationMap, currentElement, originalElement, null);
550                        nestedFinalizer.ValidateOrCreateImplementationMap(mapEntry);
551                    }
552                }
553            }
554
555            internal static void FillEnvironmentMapMemberCounts(EnvironmentUpdateMap envMap, Activity currentElement, Activity originalElement, IList<ArgumentInfo> oldArguments)
556            {
557                envMap.NewVariableCount = currentElement.RuntimeVariables != null ? currentElement.RuntimeVariables.Count : 0;
558                envMap.NewPrivateVariableCount = currentElement.ImplementationVariables != null ? currentElement.ImplementationVariables.Count : 0;
559                envMap.NewArgumentCount = currentElement.RuntimeArguments != null ? currentElement.RuntimeArguments.Count : 0;
560
561                envMap.OldVariableCount = originalElement.RuntimeVariables.Count;
562                envMap.OldPrivateVariableCount = originalElement.ImplementationVariables.Count;
563                envMap.OldArgumentCount = oldArguments != null ? oldArguments.Count : 0;
564
565                Fx.Assert((originalElement.HandlerOf == null && currentElement.HandlerOf == null)
566                    || (originalElement.HandlerOf.RuntimeDelegateArguments.Count == currentElement.HandlerOf.RuntimeDelegateArguments.Count),
567                    "RuntimeDelegateArguments count must not have changed.");
568                envMap.RuntimeDelegateArgumentCount = originalElement.HandlerOf == null ? 0 : originalElement.HandlerOf.RuntimeDelegateArguments.Count;
569            }
570
571            DynamicUpdateMapEntry CreateMapEntry(Activity currentActivity, Activity matchin----ginal)
572            {
573                Fx.Assert(currentActivity != null && matchin----ginal != null, "this entry creation is only for existing activity's ID change.");
574
575                this.foundOriginalElements[matchin----ginal.InternalId - 1] = true;
576
577                DynamicUpdateMapEntry entry = new DynamicUpdateMapEntry(matchin----ginal.InternalId, currentActivity.InternalId);
578                this.updateMap.AddEntry(entry);
579                return entry;
580            }
581
582            internal void OnCreateDynamicUpdateMap(Activity currentElement, Activity originalElement,
583                DynamicUpdateMapEntry mapEntry, IDefinitionMatcher matcher)
584            {
585                this.AllowUpdateInsideCurrentActivity = null;
586                this.UpdateDisallowedReason = null;
587                this.SavedOriginalValuesForCurrentActivity = null;
588                this.savedOriginalValuesForReferencedChildren = false;
589                currentElement.OnInternalCreateDynamicUpdateMap(this, matcher, originalElement);
590                if (this.AllowUpdateInsideCurrentActivity == false)
591                {
592                    this.BlockUpdate(currentElement, originalElement.Id, UpdateBlockedReason.Custom, mapEntry, this.UpdateDisallowedReason);
593                }
594                if (this.SavedOriginalValuesForCurrentActivity != null && this.SavedOriginalValuesForCurrentActivity.Count > 0)
595                {
596                    mapEntry.SavedOriginalValues = this.SavedOriginalValuesForCurrentActivity;
597                }
598                if (this.savedOriginalValuesForReferencedChildren)
599                {
600                    this.BlockUpdate(currentElement, originalElement.Id, UpdateBlockedReason.SavedOriginalValuesForReferencedChildren, mapEntry);
601                }
602            }
603
604            void CreateVariableEntries(bool forImplementationVariables, DynamicUpdateMapEntry mapEntry, IList<Variable> newVariables, IList<Variable> oldVariables, Activity originalElement)
605            {
606                if (newVariables != null && newVariables.Count > 0)
607                {
608                    for (int i = 0; i < newVariables.Count; i++)
609                    {
610                        Variable newVariable = newVariables[i];
611                        int originalIndex = this.Matcher.GetMatchIndex(newVariable, originalElement, forImplementationVariables);
612
613                        if (originalIndex != i)
614                        {
615                            EnsureEnvironmentUpdateMap(mapEntry);
616                            EnvironmentUpdateMapEntry environmentEntry = new EnvironmentUpdateMapEntry
617                            {
618                                OldOffset = originalIndex,
619                                NewOffset = i,
620                            };
621
622                            if (forImplementationVariables)
623                            {
624                                mapEntry.EnvironmentUpdateMap.PrivateVariableEntries.Add(environmentEntry);
625                            }
626                            else
627                            {
628                                mapEntry.EnvironmentUpdateMap.VariableEntries.Add(environmentEntry);
629                            }
630
631                            if (originalIndex == EnvironmentUpdateMapEntry.NonExistent)
632                            {
633                                Activity idleActivity = GetIdleActivity(newVariable.Default);
634                                if (idleActivity != null)
635                                {
636                                    // If an variable default expression goes idle, the activity it is declared on can potentially
637                                    // resume execution before the default expression is evaluated. We can't allow that.
638                                    this.BlockUpdate(newVariable.Owner, UpdateBlockedReason.AddedIdleExpression, mapEntry,
639                                        SR.AddedIdleVariableDefaultBlockDU(newVariable.Name, idleActivity));
640                                }
641                                else if (newVariable.IsHandle)
642                                {
643                                    this.BlockUpdate(newVariable.Owner, UpdateBlockedReason.NewHandle, mapEntry);
644                                }
645                                environmentEntry.IsNewHandle = newVariable.IsHandle;
646                            }
647                        }
648                    }
649                }
650
651                // We don't normally create entries for removals, but we need to ensure that
652                // environment update happens if there are only removals.
653                if (oldVariables != null && (newVariables == null || newVariables.Count < oldVariables.Count))
654                {
655                    EnsureEnvironmentUpdateMap(mapEntry);
656                }
657            }
658
659            internal void CreateArgumentEntries(DynamicUpdateMapEntry mapEntry, IList<RuntimeArgument> newArguments, IList<ArgumentInfo> oldArguments)
660            {
661                RuntimeArgument newIdleArgument;
662                Activity idleActivity;
663                if (!CreateArgumentEntries(mapEntry, newArguments, oldArguments, this.expressionRootsThatCanInduceIdle, out newIdleArgument, out idleActivity))
664                {
665                    // If an argument expression goes idle, the activity it is declared on can potentially
666                    // resume execution before the argument is evaluated. We can't allow that.
667                    this.BlockUpdate(newIdleArgument.Owner, UpdateBlockedReason.AddedIdleExpression, mapEntry,
668                        SR.AddedIdleArgumentBlockDU(newIdleArgument.Name, idleActivity));
669                    return;
670                }
671            }
672
673            // if it detects any added argument whose Expression can induce idle, it returns FALSE along with newIdleArgument and idleActivity.  Return true otherwise.
674            internal static bool CreateArgumentEntries(DynamicUpdateMapEntry mapEntry, IList<RuntimeArgument> newArguments, IList<ArgumentInfo> oldArguments, Dictionary<Activity, Activity> expressionRootsThatCanInduceIdle, out RuntimeArgument newIdleArgument, out Activity idleActivity)
675            {
676                newIdleArgument = null;
677                idleActivity = null;
678
679                if (newArguments != null && newArguments.Count > 0)
680                {
681                    for (int i = 0; i < newArguments.Count; i++)
682                    {
683                        RuntimeArgument newArgument = newArguments[i];
684                        int oldIndex = oldArguments.IndexOf(new ArgumentInfo(newArgument));
685                        Fx.Assert(oldIndex >= 0 || oldIndex == EnvironmentUpdateMapEntry.NonExistent, "NonExistent constant should be consistent with IndexOf");
686
687                        if (oldIndex != i)
688                        {
689                            EnsureEnvironmentUpdateMap(mapEntry);
690                            mapEntry.EnvironmentUpdateMap.ArgumentEntries.Add(new EnvironmentUpdateMapEntry
691                            {
692                                OldOffset = oldIndex,
693                                NewOffset = i
694                            });
695
696                            if (oldIndex == EnvironmentUpdateMapEntry.NonExistent && newArgument.IsBound)
697                            {
698                                Activity expressionRoot = newArgument.BoundArgument.Expression;
699                                if (expressionRoot != null && expressionRootsThatCanInduceIdle != null && expressionRootsThatCanInduceIdle.TryGetValue(expressionRoot, out idleActivity))
700                                {
701                                    newIdleArgument = newArgument;
702                                    return false;
703                                }
704                            }
705                        }
706                    }
707                }
708
709                // We don't normally create entries for removals, but we need to ensure that
710                // environment update happens if there are only removals.
711                if (oldArguments != null && (newArguments == null || newArguments.Count < oldArguments.Count))
712                {
713                    EnsureEnvironmentUpdateMap(mapEntry);
714                }
715
716                return true;
717            }
718
719            IList<ArgumentInfo> GetOriginalArguments(DynamicUpdateMapEntry mapEntry, DynamicUpdateMap implementationMap, Activity updatedActivity, Activity originalActivity)
720            {
721                bool argumentsChangedFromImplementationMap = false;
722
723                if (implementationMap != null && !implementationMap.ArgumentsAreUnknown)
724                {
725                    argumentsChangedFromImplementationMap = !ActivityComparer.ListEquals(implementationMap.NewArguments, implementationMap.OldArguments);
726                    bool dynamicArgumentsDetected = !ActivityComparer.ListEquals(ArgumentInfo.List(updatedActivity), implementationMap.NewArguments);
727                    if (argumentsChangedFromImplementationMap && dynamicArgumentsDetected)
728                    {
729                        // this is to ensure no dynamic arguments were added, removed or rearranged as the arguments owning activity was being consumed
730                        // at the same time the activity has arguments changed from its implementation map.
731                        // the list of RuntimeArguments obtained from the configured activity and the list of ArgumentInfos obtained from
732                        // the implementation map must match exactly.  Otherwise this activity is blocked for update.
733                        this.BlockUpdate(updatedActivity, UpdateBlockedReason.DynamicArguments, mapEntry, SR.NoDynamicArgumentsInActivityDefinitionChange);
734                        return null;
735                    }
736                }
737
738                return argumentsChangedFromImplementationMap ? implementationMap.OldArguments : ArgumentInfo.List(originalActivity);
739            }
740
741            Activity GetIdleActivity(Activity expressionRoot)
742            {
743                Activity result = null;
744                if (expressionRoot != null && this.expressionRootsThatCanInduceIdle != null)
745                {
746                    this.expressionRootsThatCanInduceIdle.TryGetValue(expressionRoot, out result);
747                }
748                return result;
749            }
750
751            static void EnsureEnvironmentUpdateMap(DynamicUpdateMapEntry mapEntry)
752            {
753                if (!mapEntry.HasEnvironmentUpdates)
754                {
755                    mapEntry.EnvironmentUpdateMap = new EnvironmentUpdateMap();
756                }
757            }
758
759            void CheckForReusedActivity(Activity activity)
760            {
761                if (activity.RootActivity != this.builder.OriginalWorkflowDefinition)
762                {
763                    throw FxTrace.Exception.AsError(new InvalidWorkflowException(SR.OriginalActivityReusedInModifiedDefinition(activity)));
764                }
765
766                IList<Variable> variables = activity.RuntimeVariables;
767                for (int i = 0; i < variables.Count; i++)
768                {
769                    if (variables[i].Owner.RootActivity != this.builder.OriginalWorkflowDefinition)
770                    {
771                        throw FxTrace.Exception.AsError(new InvalidWorkflowException(SR.OriginalVariableReusedInModifiedDefinition(variables[i].Name)));
772                    }
773                }
774            }
775
776            void CheckCanArgumentOrVariableDefaultInduceIdle(ActivityUtilities.ChildActivity childActivity, ActivityUtilities.ActivityCallStack parentChain)
777            {
778                Activity activity = childActivity.Activity;
779                if (!(activity.IsExpressionRoot || activity.RelationshipToParent == Activity.RelationshipType.VariableDefault))
780                {
781                    return;
782                }
783
784                if (activity.HasNonEmptySubtree)
785                {
786                    ActivityUtilities.FinishCachingSubtree(
787                        childActivity, parentChain, ProcessTreeOptions(this.builder.ForImplementation),
788                        (a, c) => CheckCanActivityInduceIdle(activity, a.Activity));
789                }
790                else
791                {
792                    CheckCanActivityInduceIdle(activity, activity);
793                }
794            }
795
796            void CheckCanActivityInduceIdle(Activity activity, Activity expressionRoot)
797            {
798                if (activity.InternalCanInduceIdle)
799                {
800                    if (this.expressionRootsThatCanInduceIdle == null)
801                    {
802                        this.expressionRootsThatCanInduceIdle = new Dictionary<Activity, Activity>(ReferenceEqualityComparer.Instance);
803                    }
804                    if (!this.expressionRootsThatCanInduceIdle.ContainsKey(expressionRoot))
805                    {
806                        this.expressionRootsThatCanInduceIdle.Add(expressionRoot, activity);
807                    }
808                }
809            }
810
811            bool DelegateArgumentsChanged(Activity newActivity, Activity oldActivity)
812            {
813                // check DelegateArguments of ActivityDelegate owning the handler
814
815                if (newActivity.HandlerOf == null)
816                {
817                    Fx.Assert(oldActivity.HandlerOf == null, "Once two activities have been matched, either both must be handlers or both must not be handlers.");
818                    return false;
819                }
820
821                Fx.Assert(oldActivity.HandlerOf != null, "Once two activities have been matched, either both must be handlers or both must not be handlers.");
822
823                return !ActivityComparer.ListEquals(newActivity.HandlerOf.RuntimeDelegateArguments, oldActivity.HandlerOf.RuntimeDelegateArguments);
824            }
825
826            void ThrowInvalidActivityToBlockUpdate(Activity activity)
827            {
828                Exception exception;
829                if (builder.OnInvalidActivityToBlockUpdate != null)
830                {
831                    exception = builder.OnInvalidActivityToBlockUpdate(activity);
832                }
833                else
834                {
835                    exception = new InvalidOperationException(SR.InvalidActivityToBlockUpdate(activity));
836                }
837                throw FxTrace.Exception.AsError(exception);
838            }
839
840            internal void ThrowInvalidImplementationMapAssociation(Activity activity)
841            {
842                Exception exception;
843                if (builder.OnInvalidImplementationMapAssociation != null)
844                {
845                    exception = builder.OnInvalidImplementationMapAssociation(activity);
846                }
847                else
848                {
849                    exception = new InvalidOperationException(SR.InvalidImplementationMapAssociation(activity));
850                }
851                throw FxTrace.Exception.AsError(exception);
852            }
853        }
854
855        internal interface IDefinitionMatcher
856        {
857            void AddMatch(Activity newChild, Activity oldChild, Activity source);
858
859            void AddMatch(Variable newVariable, Variable oldVariable, Activity source);
860
861            Activity GetMatch(Activity newActivity);
862
863            Variable GetMatch(Variable newVariable);
864        }
865
866        internal class DefinitionMatcher : IDefinitionMatcher
867        {
868            Dictionary<object, object> newToOldMatches;
869            Func<object, DynamicUpdateMapItem> matchInfoLookup;
870
871            internal DefinitionMatcher(Func<object, DynamicUpdateMapItem> matchInfoLookup)
872            {
873                this.matchInfoLookup = matchInfoLookup;
874                this.newToOldMatches = new Dictionary<object, object>(ReferenceEqualityComparer.Instance);
875            }
876
877            internal IdSpace NewIdSpace
878            {
879                get;
880                set;
881            }
882
883            internal IdSpace OldIdSpace
884            {
885                get;
886                set;
887            }
888
889            // The following methods are intended to be called by the activity author
890            // (via UpdateMapMetadata), and should validate accordingly
891            public void AddMatch(Activity newChild, Activity oldChild, Activity source)
892            {
893                Fx.Assert(source != null, "source cannot be null.");
894
895                if (newChild.Parent != source)
896                {
897                    throw FxTrace.Exception.Argument("newChild", SR.AddMatchActivityNewParentMismatch(
898                        source, newChild, newChild.Parent));
899                }
900                if (newChild.MemberOf != newChild.Parent.MemberOf)
901                {
902                    throw FxTrace.Exception.Argument("newChild", SR.AddMatchActivityPrivateChild(newChild));
903                }
904                if (oldChild.Parent != null && oldChild.MemberOf != oldChild.Parent.MemberOf)
905                {
906                    throw FxTrace.Exception.Argument("oldChild", SR.AddMatchActivityPrivateChild(oldChild));
907                }
908                if (!ParentsMatch(newChild, oldChild))
909                {
910                    throw FxTrace.Exception.Argument("oldChild", SR.AddMatchActivityNewAndOldParentMismatch(
911                        newChild, oldChild, newChild.Parent, oldChild.Parent));
912                }
913
914                // Only one updated activity can match a given original activity
915                foreach (Activity newSibling in GetPublicDeclaredChildren(newChild.Parent, true))
916                {
917                    if (GetMatch(newSibling) == oldChild)
918                    {
919                        this.newToOldMatches[newSibling] = null;
920                        break;
921                    }
922                }
923
924                this.newToOldMatches[newChild] = oldChild;
925            }
926
927            public void AddMatch(Variable newVariable, Variable oldVariable, Activity source)
928            {
929                if (!ActivityComparer.SignatureEquals(newVariable, oldVariable))
930                {
931                    throw FxTrace.Exception.Argument("newVariable", SR.AddMatchVariableSignatureMismatch(
932                        source, newVariable.Name, newVariable.Type, newVariable.Modifiers, oldVariable.Name, oldVariable.Type, oldVariable.Modifiers));
933                }
934
935                if (newVariable.Owner != source)
936                {
937                    throw FxTrace.Exception.Argument("newVariable", SR.AddMatchVariableNewParentMismatch(
938                        source, newVariable.Name, newVariable.Owner));
939                }
940                if (GetMatch(newVariable.Owner) != oldVariable.Owner)
941                {
942                    throw FxTrace.Exception.Argument("oldVariable", SR.AddMatchVariableNewAndOldParentMismatch(
943                        newVariable.Name, oldVariable.Name, newVariable.Owner, oldVariable.Owner));
944                }
945                if (!newVariable.IsPublic)
946                {
947                    throw FxTrace.Exception.Argument("newVariable", SR.AddMatchVariablePrivateChild(newVariable.Name));
948                }
949                if (!oldVariable.IsPublic)
950                {
951                    throw FxTrace.Exception.Argument("oldVariable", SR.AddMatchVariablePrivateChild(oldVariable.Name));
952                }
953
954                // Only one updated variable can match a given original variable
955                foreach (Variable newSibling in newVariable.Owner.RuntimeVariables)
956                {
957                    if (GetMatch(newSibling) == oldVariable)
958                    {
959                        this.newToOldMatches[newSibling] = EnvironmentUpdateMapEntry.NonExistent;
960                        break;
961                    }
962                }
963
964                this.newToOldMatches[newVariable] = oldVariable.Owner.RuntimeVariables.IndexOf(oldVariable);
965            }
966
967            public Activity GetMatch(Activity newChild)
968            {
969                object result;
970                if (this.newToOldMatches.TryGetValue(newChild, out result))
971                {
972                    return (Activity)result;
973                }
974
975                if (newChild.MemberOf != this.NewIdSpace)
976                {
977                    // We can only match the IdSpace being updated.
978                    return null;
979                }
980
981                if (newChild.Origin != null && newChild.RelationshipToParent == Activity.RelationshipType.VariableDefault)
982                {
983                    // Auto-generated variable defaults have the same origin as the variable itself,
984                    // so the match info comes from the variable.
985                    foreach (Variable variable in newChild.Parent.RuntimeVariables)
986                    {
987                        if (variable.Default == newChild)
988                        {
989                            Variable originalVariable = GetMatch(variable);
990                            if (originalVariable != null && originalVariable.Origin != null)
991                            {
992                                return originalVariable.Default;
993                            }
994                        }
995                    }
996
997                    return null;
998                }
999
1000                DynamicUpdateMapItem matchInfo = this.matchInfoLookup(newChild.Origin ?? newChild);
1001                if (matchInfo == null || matchInfo.IsVariableMapItem)
1002                {
1003                    return null;
1004                }
1005
1006                Activity originalActivity = this.OldIdSpace[matchInfo.OriginalId];
1007                if (originalActivity != null && ParentsMatch(newChild, originalActivity))
1008                {
1009                    this.newToOldMatches.Add(newChild, originalActivity);
1010                    return originalActivity;
1011                }
1012                else
1013                {
1014                    return null;
1015                }
1016            }
1017
1018            public Variable GetMatch(Variable newVariable)
1019            {
1020                Activity matchingOwner = GetMatch(newVariable.Owner);
1021                if (matchingOwner == null)
1022                {
1023                    return null;
1024                }
1025
1026                int index = GetMatchIndex(newVariable, matchingOwner, false);
1027                if (index >= 0)
1028                {
1029                    return matchingOwner.RuntimeVariables[index];
1030                }
1031
1032                return null;
1033            }
1034
1035            // return -1 if there is no match
1036            internal int GetMatchIndex(Variable newVariable, Activity matchingOwner, bool forImplementation)
1037            {
1038                object result;
1039                if (this.newToOldMatches.TryGetValue(newVariable, out result))
1040                {
1041                    return (int)result;
1042                }
1043
1044                IList<Variable> originalVariables;
1045                if (forImplementation)
1046                {
1047                    originalVariables = matchingOwner.ImplementationVariables;
1048                }
1049                else
1050                {
1051                    originalVariables = matchingOwner.RuntimeVariables;
1052                }
1053
1054                int oldIndex = -1;
1055                if (String.IsNullOrEmpty(newVariable.Name))
1056                {
1057                    if (forImplementation)
1058                    {
1059                        // HasPrivateMemberChanged must have detected any presence of nameless private variable in advance.
1060                        oldIndex = newVariable.Owner.ImplementationVariables.IndexOf(newVariable);
1061                    }
1062                    else
1063                    {
1064                        // only for those variables without names, we attempt to match by MapItem tag
1065                        DynamicUpdateMapItem matchInfo = this.matchInfoLookup(newVariable.Origin ?? newVariable);
1066                        if (matchInfo != null && matchInfo.IsVariableMapItem && matchingOwner.InternalId == matchInfo.OriginalVariableOwnerId)
1067                        {
1068                            // "matchingOwner.InternalId != matchInfo.OriginalVariableOwnerId" means the variable has been moved to a different owner,
1069                            // and it is treated as a new variable addition.
1070                            oldIndex = matchInfo.OriginalId;
1071                        }
1072                    }
1073                }
1074                else
1075                {
1076                    // named variables are matched by their Name, Type and Modifiers
1077
1078                    for (int i = 0; i < originalVariables.Count; i++)
1079                    {
1080                        if (ActivityComparer.SignatureEquals(newVariable, originalVariables[i]))
1081                        {
1082                            // match by sig----ure(Name, Type, Modifier) found
1083                            oldIndex = i;
1084                            break;
1085                        }
1086                    }
1087                }
1088
1089                if (oldIndex >= 0 && oldIndex < originalVariables.Count)
1090                {
1091                    this.newToOldMatches.Add(newVariable, oldIndex);
1092                    return oldIndex;
1093                }
1094
1095                return EnvironmentUpdateMapEntry.NonExistent;
1096            }
1097
1098            bool ParentsMatch(Activity currentActivity, Activity originalActivity)
1099            {
1100                if (currentActivity.Parent == null)
1101                {
1102                    return originalActivity.Parent == null;
1103                }
1104                else
1105                {
1106                    if (currentActivity.RelationshipToParent != originalActivity.RelationshipToParent ||
1107                        (currentActivity.HandlerOf != null && currentActivity.HandlerOf.ParentCollectionType != originalActivity.HandlerOf.ParentCollectionType))
1108                    {
1109                        return false;
1110                    }
1111
1112                    if (currentActivity.Parent == currentActivity.MemberOf.Owner)
1113                    {
1114                        return originalActivity.Parent == this.OldIdSpace.Owner;
1115                    }
1116
1117                    return originalActivity.Parent != null &&
1118                        GetMatch(currentActivity.Parent) == originalActivity.Parent;
1119                }
1120            }
1121        }
1122
1123        internal class NestedIdSpaceFinalizer : IDefinitionMatcher
1124        {
1125            Finalizer finalizer;
1126            DynamicUpdateMap userProvidedMap;
1127            DynamicUpdateMap generatedMap;
1128            Activity updatedActivity;
1129            Activity originalActivity;
1130            bool invalidMatchInCurrentActivity;
1131            NestedIdSpaceFinalizer parent;
1132
1133            public NestedIdSpaceFinalizer(Finalizer finalizer, DynamicUpdateMap implementationMap, Activity updatedActivity, Activity originalActivity, NestedIdSpaceFinalizer parent)
1134            {
1135                this.finalizer = finalizer;
1136                this.userProvidedMap = implementationMap;
1137                this.updatedActivity = updatedActivity;
1138                this.originalActivity = originalActivity;
1139                this.parent = parent;
1140            }
1141
1142            public void ValidateOrCreateImplementationMap(DynamicUpdateMapEntry mapEntry)
1143            {
1144                // check applicability of the provided implementation map
1145                if (this.userProvidedMap != null)
1146                {
1147                    IdSpace privateIdSpace = updatedActivity.ParentOf;
1148                    if (privateIdSpace == null)
1149                    {
1150                        this.finalizer.ThrowInvalidImplementationMapAssociation(updatedActivity);
1151                    }
1152                    if (!this.userProvidedMap.IsNoChanges && privateIdSpace.MemberCount != this.userProvidedMap.NewDefinitionMemberCount)
1153                    {
1154                        BlockUpdate(updatedActivity, UpdateBlockedReason.InvalidImplementationMap, mapEntry,
1155                            SR.InvalidImplementationMap(this.userProvidedMap.NewDefinitionMemberCount, privateIdSpace.MemberCount));
1156                        return;
1157                    }
1158                }
1159
1160                // The only difference between updatedActivity and originalActivity is changes in the outer IdSpace.
1161                // The implementation IdSpace should never change in response to outer IdSpace changes.
1162                // 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.
1163                // If any argument change is detected and nothing else changed in the private IdSpace,
1164                //  HasPrivateMemberOtherThanArgumentsChanged will return FALSE as well as returning a generated implementation Map.
1165                // Also, when userProvidedMap exists, we don't allow changes to arguments inside the private IdSpace
1166                DynamicUpdateMap argumentChangesMap;
1167                if (ActivityComparer.HasPrivateMemberOtherThanArgumentsChanged(this, updatedActivity, originalActivity, this.parent == null, out argumentChangesMap) ||
1168                    (argumentChangesMap != null && this.userProvidedMap != null))
1169                {
1170                    // either of the following two must have occured here.
1171                    // A.
1172                    // members in the private IdSpace(or nested IdSpaces) must have changed.
1173                    // addition/removal/rearrangement of arguments and their expressions in the private IdSpace are not considered as change.
1174                    //
1175                    // B.
1176                    // 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.
1177                    // Due to the id shift caused by argument change, an implementation map("argumentChangesMap") was created.
1178                    // In addition to "argumentChangesMap", there is also a user provided map.  This blocks DU.
1179
1180                    // generate a warning and block update inside this activity
1181                    BlockUpdate(updatedActivity, UpdateBlockedReason.PrivateMembersHaveChanged, mapEntry);
1182                    return;
1183                }
1184
1185                if (updatedActivity.ParentOf != null)
1186                {
1187                    GenerateMap(argumentChangesMap);
1188                    if (this.generatedMap == null)
1189                    {
1190                        mapEntry.ImplementationUpdateMap = this.userProvidedMap;
1191                    }
1192                    else
1193                    {
1194                        if (this.userProvidedMap == null || this.userProvidedMap.IsNoChanges)
1195                        {
1196                            FillGeneratedMap();
1197                        }
1198                        else
1199                        {
1200                            MergeProvidedMapIntoGeneratedMap();
1201                        }
1202                        mapEntry.ImplementationUpdateMap = this.generatedMap;
1203                    }
1204                }
1205            }
1206
1207            // AddMatch is a no-op, since any matches come from the provided implementation map.
1208            // However an invalid match can still cause us to disallow update
1209            public void AddMatch(Activity newChild, Activity oldChild, Activity source)
1210            {
1211                if (newChild.Parent != source || newChild.MemberOf != source.MemberOf || GetMatch(newChild) != oldChild)
1212                {
1213                    this.invalidMatchInCurrentActivity = true;
1214                }
1215            }
1216
1217            public void AddMatch(Variable newVariable, Variable oldVariable, Activity source)
1218            {
1219                if (newVariable.Owner != source || !newVariable.IsPublic || GetMatch(newVariable) != oldVariable)
1220                {
1221                    this.invalidMatchInCurrentActivity = true;
1222                }
1223            }
1224
1225            public Activity GetMatch(Activity newActivity)
1226            {
1227                NestedIdSpaceFinalizer owningFinalizer = this;
1228                do
1229                {
1230                    // The original definition being updated still needs to reference the updated implementation.
1231                    // So even if we have a provided impl map, there should be no ID changes between updatedActivity and original activity.
1232                    if (newActivity.MemberOf == owningFinalizer.updatedActivity.ParentOf)
1233                    {
1234                        return owningFinalizer.originalActivity.ParentOf[newActivity.InternalId];
1235                    }
1236                    owningFinalizer = owningFinalizer.parent;
1237                }
1238                while (owningFinalizer != null);
1239
1240                return this.finalizer.Matcher.GetMatch(newActivity);
1241            }
1242
1243            public Variable GetMatch(Variable newVariable)
1244            {
1245                Fx.Assert(newVariable.Owner.MemberOf == this.updatedActivity.ParentOf, "Should only call GetMatch for variables owned by the participating activity");
1246                int index = newVariable.Owner.RuntimeVariables.IndexOf(newVariable);
1247                if (index >= 0)
1248                {
1249                    Activity matchingOwner = GetMatch(newVariable.Owner);
1250                    if (matchingOwner != null && matchingOwner.RuntimeVariables.Count > index)
1251                    {
1252                        return matchingOwner.RuntimeVariables[index];
1253                    }
1254                }
1255
1256                return null;
1257            }
1258
1259            public void CreateArgumentEntries(DynamicUpdateMapEntry mapEntry, IList<RuntimeArgument> newArguments, IList<ArgumentInfo> oldArguments)
1260            {
1261                RuntimeArgument newIdleArgument;
1262                Activity idleActivity;
1263                if (!DynamicUpdateMapBuilder.Finalizer.CreateArgumentEntries(mapEntry, newArguments, oldArguments, this.finalizer.ExpressionRootsThatCanInduceIdle, out newIdleArgument, out idleActivity))
1264                {
1265                    // If an argument expression goes idle, the activity it is declared on can potentially
1266                    // resume execution before the argument is evaluated. We can't allow that.
1267                    this.BlockUpdate(newIdleArgument.Owner, UpdateBlockedReason.AddedIdleExpression, mapEntry,
1268                        SR.AddedIdleArgumentBlockDU(newIdleArgument.Name, idleActivity));
1269                    return;
1270                }
1271            }
1272
1273            void BlockUpdate(Activity updatedActivity, UpdateBlockedReason reason, DynamicUpdateMapEntry entry, string message = null)
1274            {
1275                Activity originalActivity = GetMatch(updatedActivity);
1276                Fx.Assert(originalActivity != null, "Cannot block update inside an added activity");
1277                this.finalizer.BlockUpdate(updatedActivity, originalActivity.Id, reason, entry, message);
1278            }
1279
1280            // This method allows activities in the implementation IdSpace to participate in map creation.
1281            // This is necessary because they may need to save original values for properties whose value
1282            // may be set by referencing activity properties. (E.g. <Receive OperationName='{PropertyReference OpName}' />)
1283            // They may also disable update based on observed property values. However they are not allowed
1284            // to change or add any matches, because the implementation IdSpace should not be changing
1285            // based on public property changes.
1286            // if argumentChangesMap is non-null, it will be used as the initial generatedMap onto which original values are saved.
1287            void GenerateMap(DynamicUpdateMap argumentChangesMap)
1288            {
1289                IdSpace updatedIdSpace = this.updatedActivity.ParentOf;
1290                IdSpace originalIdSpace = this.originalActivity.ParentOf;
1291
1292                for (int i = 1; i <= updatedIdSpace.MemberCount; i++)
1293                {
1294                    DynamicUpdateMapEntry providedEntry = null;
1295                    if (this.userProvidedMap != null && !this.userProvidedMap.IsNoChanges)
1296                    {
1297                        bool isNewlyAdded = !this.userProvidedMap.TryGetUpdateEntryByNewId(i, out providedEntry);
1298                        if (isNewlyAdded || providedEntry.IsRuntimeUpdateBlocked ||
1299                            providedEntry.IsUpdateBlockedByUpdateAuthor || providedEntry.IsParentRemovedOrBlocked)
1300                        {
1301                            // No need to save original values or block update
1302                            continue;
1303                        }
1304                    }
1305
1306                    DynamicUpdateMapEntry argumentChangesMapEntry = null;
1307                    if (argumentChangesMap != null)
1308                    {
1309                        Fx.Assert(!argumentChangesMap.IsNoChanges, "argumentChangesMap will never be NoChanges map because it is automatically created only when there is argument changes.");
1310                        bool isNewlyAdded = !argumentChangesMap.TryGetUpdateEntryByNewId(i, out argumentChangesMapEntry);
1311                        if (isNewlyAdded)
1312                        {
1313                            // No need to save original values or block update
1314                            continue;
1315                        }
1316                    }
1317
1318                    // We only need to save this map entry if it has some non-default value.
1319                    DynamicUpdateMapEntry generatedEntry = GenerateEntry(argumentChangesMapEntry, providedEntry, i);
1320                    DynamicUpdateMap providedImplementationMap = providedEntry != null ? providedEntry.ImplementationUpdateMap : null;
1321                    if (generatedEntry.IsRuntimeUpdateBlocked ||
1322                        generatedEntry.SavedOriginalValues != null ||
1323                        generatedEntry.SavedOriginalValueFromParent != null ||
1324                        generatedEntry.ImplementationUpdateMap != providedImplementationMap ||
1325                        generatedEntry.IsIdChange ||
1326                        generatedEntry.HasEnvironmentUpdates)
1327                    {
1328                        EnsureGeneratedMap();
1329                        this.generatedMap.AddEntry(generatedEntry);
1330                    }
1331                }
1332
1333                if (argumentChangesMap != null && argumentChangesMap.entries != null)
1334                {
1335                    // add all IsRemoved entries
1336                    foreach (DynamicUpdateMapEntry entry in argumentChangesMap.entries)
1337                    {
1338                        if (entry.IsRemoval)
1339                        {
1340                            EnsureGeneratedMap();
1341                            this.generatedMap.AddEntry(entry);
1342                        }
1343                    }
1344                }
1345            }
1346
1347            void EnsureGeneratedMap()
1348            {
1349                if (this.generatedMap == null)
1350                {
1351                    this.generatedMap = new DynamicUpdateMap
1352                    {
1353                        IsForImplementation = true,
1354                        NewDefinitionMemberCount = this.updatedActivity.ParentOf.MemberCount
1355                    };
1356                }
1357            }
1358
1359            DynamicUpdateMapEntry GenerateEntry(DynamicUpdateMapEntry argumentChangesMapEntry, DynamicUpdateMapEntry providedEntry, int id)
1360            {
1361                DynamicUpdateMapEntry generatedEntry;
1362                Activity updatedChild;
1363                Activity originalChild;
1364
1365                // argumentChangesMapEntry and providedEntry are mutually exclusive.
1366                // both cannot be non-null at the same time although both may be null at the same time.
1367                if (argumentChangesMapEntry == null)
1368                {
1369                    int originalIndex = providedEntry != null ? providedEntry.OldActivityId : id;
1370                    generatedEntry = new DynamicUpdateMapEntry(originalIndex, id);
1371
1372                    // we assume nothing has changed in the private IdSpace
1373                    updatedChild = this.updatedActivity.ParentOf[id];
1374                    originalChild = this.originalActivity.ParentOf[id];
1375                }
1376                else
1377                {
1378                    generatedEntry = argumentChangesMapEntry;
1379
1380                    // activity IDs in the private IdSpace has changed due to arguments change inside the private IdSpace
1381                    updatedChild = this.updatedActivity.ParentOf[argumentChangesMapEntry.NewActivityId];
1382                    originalChild = this.originalActivity.ParentOf[argumentChangesMapEntry.OldActivityId];
1383                }
1384
1385                // Allow the activity to participate
1386                this.invalidMatchInCurrentActivity = false;
1387                this.finalizer.OnCreateDynamicUpdateMap(updatedChild, originalChild, generatedEntry, this);
1388                if (this.invalidMatchInCurrentActivity && !generatedEntry.IsRuntimeUpdateBlocked)
1389                {
1390                    BlockUpdate(updatedChild, UpdateBlockedReason.ChangeMatchesInImplementation, generatedEntry);
1391                }
1392
1393                // Fill in the rest of the map entry;
1394                generatedEntry.SavedOriginalValueFromParent = this.finalizer.GetSavedOriginalValueFromParent(updatedChild);
1395                DynamicUpdateMap childImplementationMap = providedEntry != null ? providedEntry.ImplementationUpdateMap : null;
1396                if (!generatedEntry.IsRuntimeUpdateBlocked)
1397                {
1398                    NestedIdSpaceFinalizer nestedFinalizer = new NestedIdSpaceFinalizer(this.finalizer, childImplementationMap, updatedChild, originalChild, this);
1399                    nestedFinalizer.ValidateOrCreateImplementationMap(generatedEntry);
1400                }
1401
1402                return generatedEntry;
1403            }
1404
1405            // The generated map only contains entries that have some non-default value. For it to be a valid
1406            // implementation map, we need to fill in all the unchanged entries.
1407            void FillGeneratedMap()
1408            {
1409                Fx.Assert(this.generatedMap != null, "If there were no generated entries then we don't need a generated map.");
1410                this.generatedMap.ArgumentsAreUnknown = true;
1411                for (int i = 1; i <= this.originalActivity.ParentOf.MemberCount; i++)
1412                {
1413                    DynamicUpdateMapEntry entry;
1414                    if (!this.generatedMap.TryGetUpdateEntry(i, out entry))
1415                    {
1416                        entry = new DynamicUpdateMapEntry(i, i);
1417                        this.generatedMap.AddEntry(entry);
1418                    }
1419                    entry.Parent = GetParentEntry(this.originalActivity.ParentOf[i], this.generatedMap);
1420                }
1421            }
1422
1423            void MergeProvidedMapIntoGeneratedMap()
1424            {
1425                this.generatedMap.OldArguments = this.userProvidedMap.OldArguments;
1426                this.generatedMap.NewArguments = this.userProvidedMap.NewArguments;
1427
1428                for (int i = 1; i <= this.userProvidedMap.OldDefinitionMemberCount; i++)
1429                {
1430                    // Get/create the matching generated entry
1431                    DynamicUpdateMapEntry providedEntry;
1432                    this.userProvidedMap.TryGetUpdateEntry(i, out providedEntry);
1433                    DynamicUpdateMapEntry generatedEntry = GetOrCreateGeneratedEntry(providedEntry);
1434                    if (generatedEntry.IsRemoval || generatedEntry.IsRuntimeUpdateBlocked || generatedEntry.IsUpdateBlockedByUpdateAuthor || generatedEntry.IsParentRemovedOrBlocked)
1435                    {
1436                        continue;
1437                    }
1438
1439                    // Disable update if there's a conflict
1440                    int newActivityId = providedEntry.NewActivityId;
1441                    if (HasOverlap(providedEntry.SavedOriginalValues, generatedEntry.SavedOriginalValues) ||
1442                        (HasSavedOriginalValuesForChildren(newActivityId, this.userProvidedMap) && HasSavedOriginalValuesForChildren(newActivityId, this.generatedMap)))
1443                    {
1444                        Activity updatedChild = this.updatedActivity.ParentOf[generatedEntry.NewActivityId];
1445                        BlockUpdate(updatedChild, UpdateBlockedReason.GeneratedAndProvidedMapConflict, generatedEntry, SR.GeneratedAndProvidedMapConflict);
1446                    }
1447                    else
1448                    {
1449                        generatedEntry.SavedOriginalValues = DynamicUpdateMapEntry.Merge(generatedEntry.SavedOriginalValues, providedEntry.SavedOriginalValues);
1450                    }
1451                }
1452            }
1453
1454            DynamicUpdateMapEntry GetOrCreateGeneratedEntry(DynamicUpdateMapEntry providedEntry)
1455            {
1456                // Get or create the matching entry
1457                DynamicUpdateMapEntry generatedEntry;
1458                if (!this.generatedMap.TryGetUpdateEntry(providedEntry.OldActivityId, out generatedEntry))
1459                {
1460                    generatedEntry = new DynamicUpdateMapEntry(providedEntry.OldActivityId, providedEntry.NewActivityId)
1461                    {
1462                        DisplayName = providedEntry.DisplayName,
1463                        BlockReason = providedEntry.BlockReason,
1464                        BlockReasonMessage = providedEntry.BlockReasonMessage,
1465                        IsUpdateBlockedByUpdateAuthor = providedEntry.IsUpdateBlockedByUpdateAuthor,
1466                    };
1467                    this.generatedMap.AddEntry(generatedEntry);
1468                }
1469                else
1470                {
1471                    Fx.Assert(providedEntry.NewActivityId == generatedEntry.NewActivityId &&
1472                        providedEntry.DisplayName == generatedEntry.DisplayName &&
1473                        !providedEntry.IsRuntimeUpdateBlocked && !providedEntry.IsUpdateBlockedByUpdateAuthor,
1474                        "GeneratedEntry should be created with correct ID, and should not be created for an entry that has blocked update");
1475                }
1476
1477                // Copy/fill additional values
1478                generatedEntry.EnvironmentUpdateMap = providedEntry.EnvironmentUpdateMap;
1479                if (providedEntry.Parent != null)
1480                {
1481                    DynamicUpdateMapEntry parentEntry;
1482                    this.generatedMap.TryGetUpdateEntry(providedEntry.Parent.OldActivityId, out parentEntry);
1483                    Fx.Assert(parentEntry != null, "We process in IdSpace order, so we always process parents before their children");
1484                    generatedEntry.Parent = parentEntry;
1485                }
1486                if (generatedEntry.SavedOriginalValueFromParent == null)
1487                {
1488                    generatedEntry.SavedOriginalValueFromParent = providedEntry.SavedOriginalValueFromParent;
1489                }
1490                if (generatedEntry.ImplementationUpdateMap == null)
1491                {
1492                    generatedEntry.ImplementationUpdateMap = providedEntry.ImplementationUpdateMap;
1493                }
1494
1495                return generatedEntry;
1496            }
1497
1498            bool HasOverlap(IDictionary<string, object> providedValues, IDictionary<string, object> generatedValues)
1499            {
1500                return providedValues != null && generatedValues != null &&
1501                    providedValues.Keys.Any(k => generatedValues.ContainsKey(k));
1502            }
1503
1504            bool HasSavedOriginalValuesForChildren(int parentNewActivityId, DynamicUpdateMap map)
1505            {
1506                foreach (Activity child in GetPublicDeclaredChildren(this.updatedActivity.ParentOf[parentNewActivityId], false))
1507                {
1508                    DynamicUpdateMapEntry childEntry;
1509                    if (map.TryGetUpdateEntryByNewId(child.InternalId, out childEntry) &&
1510                        childEntry.SavedOriginalValueFromParent != null)
1511                    {
1512                        return true;
1513                    }
1514                }
1515
1516                return false;
1517            }
1518        }
1519    }
1520}
1521