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