File: System\Activities\Statements\Pick.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.Activities.Statements
{
    using System.Activities.DynamicUpdate;
    using System.Activities.Validation;
    using System.Collections.ObjectModel;
    using System.Runtime;
    using System.Runtime.Collections;
    using System.Runtime.Serialization;
    using System.Windows.Markup;
 
    [ContentProperty("Branches")]
    public sealed class Pick : NativeActivity
    {
        const string pickStateProperty = "System.Activities.Statements.Pick.PickState";
 
        Collection<PickBranch> branches;
        Variable<PickState> pickStateVariable;
        Collection<Activity> branchBodies;        
        
        public Pick()
        {
            this.pickStateVariable = new Variable<PickState>();
        }
 
        protected override bool CanInduceIdle
        {
            get
            {
                return true;
            }
        }
 
        public Collection<PickBranch> Branches
        {
            get
            {
                if (this.branches == null)
                {
                    this.branches = new ValidatingCollection<PickBranch>
                    {
                        // disallow null values
                        OnAddValidationCallback = item =>
                        {
                            if (item == null)
                            {
                                throw FxTrace.Exception.ArgumentNull("item");
                            }
                        }
                    };
                }
                return this.branches;
            }
        }
 
        protected override void OnCreateDynamicUpdateMap(NativeActivityUpdateMapMetadata metadata, Activity originalActivity)
        {
            metadata.AllowUpdateInsideThisActivity();
        }
 
        protected override void UpdateInstance(NativeActivityUpdateContext updateContext)
        {
            PickState pickState = updateContext.GetValue(this.pickStateVariable);
            Fx.Assert(pickState != null, "Pick's Execute must have run by now.");
 
            if (updateContext.IsCancellationRequested || pickState.TriggerCompletionBookmark == null)
            {
                // do not schedule newly added Branches once a Trigger has successfully completed.
                return;
            }            
 
            CompletionCallback onBranchCompleteCallback = new CompletionCallback(OnBranchComplete);
            foreach (PickBranchBody body in this.branchBodies)
            {
                if (updateContext.IsNewlyAdded(body))
                {
                    updateContext.ScheduleActivity(body, onBranchCompleteCallback, null);
                }
            }
        }
 
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            if (this.branchBodies == null)
            {
                this.branchBodies = new Collection<Activity>();
            }
            else
            {
                this.branchBodies.Clear();
            }
 
            foreach (PickBranch branch in this.Branches)
            {
                if (branch.Trigger == null)
                {
                    metadata.AddValidationError(new ValidationError(SR.PickBranchRequiresTrigger(branch.DisplayName), false, null, branch));
                }
                
                PickBranchBody pickBranchBody = new PickBranchBody
                {
                    Action = branch.Action,
                    DisplayName = branch.DisplayName,
                    Trigger = branch.Trigger,
                    Variables = branch.Variables,                    
                };
 
                this.branchBodies.Add(pickBranchBody);
 
                metadata.AddChild(pickBranchBody, origin: branch);
            }
                        
            metadata.AddImplementationVariable(this.pickStateVariable);
        }
 
        protected override void Execute(NativeActivityContext context)
        {
            if (this.branchBodies.Count == 0)
            {
                 return;
            }
 
            PickState pickState = new PickState();
            this.pickStateVariable.Set(context, pickState);
 
            pickState.TriggerCompletionBookmark = context.CreateBookmark(new BookmarkCallback(OnTriggerComplete));
 
            context.Properties.Add(pickStateProperty, pickState);
 
            CompletionCallback onBranchCompleteCallback = new CompletionCallback(OnBranchComplete);
 
            //schedule every branch to only run trigger
            for (int i = this.branchBodies.Count - 1; i >= 0; i--)
            {
                context.ScheduleActivity(this.branchBodies[i], onBranchCompleteCallback);
            }
        }
 
        protected override void Cancel(NativeActivityContext context)
        {
            context.CancelChildren();
        }
 
        void OnBranchComplete(NativeActivityContext context, ActivityInstance completedInstance)
        {
            PickState pickState = this.pickStateVariable.Get(context);
            ReadOnlyCollection<ActivityInstance> executingChildren = context.GetChildren();
 
            switch (completedInstance.State)
            {
                case ActivityInstanceState.Closed:
                    pickState.HasBranchCompletedSuccessfully = true;
                    break;
                case ActivityInstanceState.Canceled:
                case ActivityInstanceState.Faulted:
                    if (context.IsCancellationRequested)
                    {
                        if (executingChildren.Count == 0 && !pickState.HasBranchCompletedSuccessfully)
                        {
                            // All of the branches are complete and we haven't had a single
                            // one complete successfully and we've been asked to cancel.
                            context.MarkCanceled();
                            context.RemoveAllBookmarks();
                        }
                    }                    
                    break;
            }
 
            //the last branch should always resume action bookmark if it's still there
            if (executingChildren.Count == 1 && pickState.ExecuteActionBookmark != null)
            {
                ResumeExecutionActionBookmark(pickState, context);
            }
        }
        
        void OnTriggerComplete(NativeActivityContext context, Bookmark bookmark, object state)
        {
            PickState pickState = this.pickStateVariable.Get(context);
 
            string winningBranch = (string)state;
 
            ReadOnlyCollection<ActivityInstance> children = context.GetChildren();
 
            bool resumeAction = true;
 
            for (int i = 0; i < children.Count; i++)
            {
                ActivityInstance child = children[i];
 
                if (child.Id != winningBranch)
                {
                    context.CancelChild(child);
                    resumeAction = false;
                }
            }
            
            if (resumeAction)
            {
                ResumeExecutionActionBookmark(pickState, context);
            }
        }
 
        void ResumeExecutionActionBookmark(PickState pickState, NativeActivityContext context)
        {
            Fx.Assert(pickState.ExecuteActionBookmark != null, "This should have been set by the branch.");
 
            context.ResumeBookmark(pickState.ExecuteActionBookmark, null);
            pickState.ExecuteActionBookmark = null;
        }
 
        [DataContract]
        internal class PickState
        {
            [DataMember(EmitDefaultValue = false)]
            public bool HasBranchCompletedSuccessfully
            {
                get;
                set;
            }
 
            [DataMember(EmitDefaultValue = false)]
            public Bookmark TriggerCompletionBookmark
            {
                get;
                set;
            }
 
            [DataMember(EmitDefaultValue = false)]
            public Bookmark ExecuteActionBookmark
            {
                get;
                set;
            }
        }
 
        class PickBranchBody : NativeActivity
        {
            public PickBranchBody()
            {
            }
 
            protected override bool CanInduceIdle
            {
                get
                {
                    return true;
                }
            }
 
            public Collection<Variable> Variables
            {
                get;
                set;
            }
 
            public Activity Trigger
            {
                get;
                set;
            }
 
            public Activity Action
            {
                get;
                set;
            }
 
            protected override void OnCreateDynamicUpdateMap(NativeActivityUpdateMapMetadata metadata, Activity originalActivity)
            {
                PickBranchBody originalBranchBody = (PickBranchBody)originalActivity;
                if ((originalBranchBody.Action != null && metadata.GetMatch(this.Trigger) == originalBranchBody.Action) || (this.Action != null && metadata.GetMatch(this.Action) == originalBranchBody.Trigger))
                {
                    metadata.DisallowUpdateInsideThisActivity(SR.PickBranchTriggerActionSwapped);
                    return;
                }
 
                metadata.AllowUpdateInsideThisActivity();
            }
 
            protected override void CacheMetadata(NativeActivityMetadata metadata)
            {
                Collection<Activity> children = null;
 
                if (this.Trigger != null)
                {
                    ActivityUtilities.Add(ref children, this.Trigger);
                }
                if (this.Action != null)
                {
                    ActivityUtilities.Add(ref children, this.Action);
                }
 
                metadata.SetChildrenCollection(children);
                
                metadata.SetVariablesCollection(this.Variables);
            }
 
            protected override void Execute(NativeActivityContext context)
            {
                Fx.Assert(this.Trigger != null, "We validate that the trigger is not null in Pick.CacheMetadata");
 
                context.ScheduleActivity(this.Trigger, new CompletionCallback(OnTriggerCompleted));
            }
 
            void OnTriggerCompleted(NativeActivityContext context, ActivityInstance completedInstance)
            {
                PickState pickState = (PickState)context.Properties.Find(pickStateProperty);
 
                if (completedInstance.State == ActivityInstanceState.Closed && pickState.TriggerCompletionBookmark != null)
                {
                    // We're the first trigger!  We win!
                    context.ResumeBookmark(pickState.TriggerCompletionBookmark, context.ActivityInstanceId);
                    pickState.TriggerCompletionBookmark = null;
                    pickState.ExecuteActionBookmark = context.CreateBookmark(new BookmarkCallback(OnExecuteAction));
                }
                else if (!context.IsCancellationRequested)
                {
                    // We didn't win, but we haven't been requested to cancel yet.
                    // We'll just create a bookmark to keep ourselves from completing.
                    context.CreateBookmark();
                }
                // else
                // {
                //     No need for an else since default cancelation will cover it!
                // }
            }
 
            void OnExecuteAction(NativeActivityContext context, Bookmark bookmark, object state)
            {
                if (this.Action != null)
                {
                    context.ScheduleActivity(this.Action);
                }
            }
        }
    }
}