File: ConstrainedGroup.cs
Project: ndp\cdf\src\WF\Activities\System.Workflow.Activities.csproj (System.Workflow.Activities)
namespace System.Workflow.Activities
{
    #region Imports
 
    using System;
    using System.Diagnostics;
    using System.Xml;
    using System.CodeDom;
    using System.Drawing;
    using System.Collections;
    using System.Windows.Forms;
    using System.ComponentModel;
    using System.Drawing.Drawing2D;
    using System.Collections.Generic;
    using System.Windows.Forms.Design;
    using System.ComponentModel.Design;
    using System.Workflow.ComponentModel;
    using System.Workflow.ComponentModel.Design;
    using System.ComponentModel.Design.Serialization;
    using System.Workflow.ComponentModel.Compiler;
    using System.Workflow.Runtime.DebugEngine;
    using System.Workflow.Activities.Common;
 
    #endregion
 
    [SRDescription(SR.ConstrainedGroupActivityDescription)]
    [ToolboxItem(typeof(ActivityToolboxItem))]
    [Designer(typeof(ConditionedActivityGroupDesigner), typeof(IDesigner))]
    [ToolboxBitmap(typeof(ConditionedActivityGroup), "Resources.cag.png")]
    [ActivityValidator(typeof(ConditionedActivityGroupValidator))]
    [SRCategory(SR.Standard)]
    [WorkflowDebuggerSteppingAttribute(WorkflowDebuggerSteppingOption.Concurrent)]
    [Obsolete("The System.Workflow.* types are deprecated.  Instead, please use the new types from System.Activities.*")]
    public sealed class ConditionedActivityGroup : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
    {
        //Attached properties provided to the children
        public static readonly DependencyProperty WhenConditionProperty = DependencyProperty.RegisterAttached("WhenCondition", typeof(ActivityCondition), typeof(ConditionedActivityGroup), new PropertyMetadata(DependencyPropertyOptions.Metadata), typeof(WhenUnlessConditionDynamicPropertyValidator));
 
        // metadata properties go here
        public static readonly DependencyProperty UntilConditionProperty = DependencyProperty.Register("UntilCondition", typeof(ActivityCondition), typeof(ConditionedActivityGroup), new PropertyMetadata(DependencyPropertyOptions.Metadata));
 
        #region Constructors
 
        public ConditionedActivityGroup()
        {
        }
 
        public ConditionedActivityGroup(string name)
            : base(name)
        {
        }
 
        #endregion
 
        // WhenConditionProperty Get and Set Accessors
        public static object GetWhenCondition(object dependencyObject)
        {
            if (dependencyObject == null)
                throw new ArgumentNullException("dependencyObject");
            if (!(dependencyObject is DependencyObject))
                throw new ArgumentException(SR.GetString(SR.Error_UnexpectedArgumentType, typeof(DependencyObject).FullName), "dependencyObject");
 
            return (dependencyObject as DependencyObject).GetValue(WhenConditionProperty);
        }
 
        public static void SetWhenCondition(object dependencyObject, object value)
        {
            if (dependencyObject == null)
                throw new ArgumentNullException("dependencyObject");
            if (!(dependencyObject is DependencyObject))
                throw new ArgumentException(SR.GetString(SR.Error_UnexpectedArgumentType, typeof(DependencyObject).FullName), "dependencyObject");
 
            (dependencyObject as DependencyObject).SetValue(WhenConditionProperty, value);
        }
 
        [SRCategory(SR.Conditions)]
        [SRDescription(SR.UntilConditionDescr)]
        [DefaultValue(null)]
        public ActivityCondition UntilCondition
        {
            get
            {
                return base.GetValue(UntilConditionProperty) as ActivityCondition;
            }
            set
            {
                base.SetValue(UntilConditionProperty, value);
            }
        }
 
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        private Activity GetDynamicActivity(Activity childActivity)
        {
            if (childActivity == null)
                throw new ArgumentNullException("childActivity");
 
            if (!this.EnabledActivities.Contains(childActivity))
                throw new ArgumentException(SR.GetString(SR.Error_CAGChildNotFound, childActivity.QualifiedName, this.QualifiedName), "childActivity");
            else
            {
                Activity[] dynamicChildActivity = this.GetDynamicActivities(childActivity);
 
                if (dynamicChildActivity.Length != 0)
                    return dynamicChildActivity[0];
                else
                    return null;
            }
        }
 
        public Activity GetDynamicActivity(String childActivityName)
        {
            if (childActivityName == null)
                throw new ArgumentNullException("childActivityName");
 
            Activity childActivity = null;
 
            for (int i = 0; i < this.EnabledActivities.Count; ++i)
            {
                if (this.EnabledActivities[i].QualifiedName.Equals(childActivityName))
                {
                    childActivity = this.EnabledActivities[i];
                    break;
                }
            }
 
            if (childActivity != null)
                return GetDynamicActivity(childActivity);
 
            throw new ArgumentException(SR.GetString(SR.Error_CAGChildNotFound, childActivityName, this.QualifiedName), "childActivityName");
        }
 
        public int GetChildActivityExecutedCount(Activity child)
        {
            if (child == null)
                throw new ArgumentNullException("child");
 
            ConditionedActivityGroupStateInfo conditionedInfo = this.CAGState;
 
            if (conditionedInfo == null)
            {
                throw new InvalidOperationException(SR.GetString(SR.Error_CAGNotExecuting, this.QualifiedName));
            }
 
            if (!conditionedInfo.ChildrenStats.ContainsKey(child.QualifiedName))
            {
                throw new ArgumentException(SR.GetString(SR.Error_CAGChildNotFound, child.QualifiedName, this.QualifiedName), "child");
            }
            else
            {
                return conditionedInfo.ChildrenStats[child.QualifiedName].ExecutedCount;
            }
        }
 
        private sealed class WhenUnlessConditionDynamicPropertyValidator : Validator
        {
            public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
            {
                ValidationErrorCollection validationErrors = ValidationHelpers.ValidateObject(manager, obj);
 
                if (validationErrors.Count == 0)
                {
                    Activity activity = manager.Context[typeof(Activity)] as Activity;
                    if (activity == null)
                        throw new InvalidOperationException(SR.GetString(SR.Error_ContextStackItemMissing, typeof(Activity).Name));
 
                    CodeCondition codeCondition = obj as CodeCondition;
                    if (codeCondition != null && codeCondition.IsBindingSet(CodeCondition.ConditionEvent))
                    {
                        ActivityBind activityBind = codeCondition.GetBinding(CodeCondition.ConditionEvent) as ActivityBind;
                        if (activityBind != null)
                        {
                            Activity contextActivity = Helpers.ParseActivityForBind(activity, activityBind.Name);
                            if (contextActivity != null && Helpers.IsChildActivity(activity.Parent, contextActivity))
                            {
                                string propertyName = GetFullPropertyName(manager);
                                ValidationError error = new ValidationError(SR.GetString(SR.Error_NestedConstrainedGroupConditions, propertyName), ErrorNumbers.Error_NestedConstrainedGroupConditions);
                                error.PropertyName = propertyName;
                                validationErrors.Add(error);
                            }
                        }
                    }
                }
 
                return validationErrors;
            }
        }
 
        #region Runtime Internal Dependency Property
        static DependencyProperty CAGStateProperty = DependencyProperty.Register("CAGState", typeof(ConditionedActivityGroupStateInfo), typeof(ConditionedActivityGroup));
 
        internal ConditionedActivityGroupStateInfo CAGState
        {
            get
            {
                return (ConditionedActivityGroupStateInfo)base.GetValue(CAGStateProperty);
            }
            set
            {
                base.SetValue(CAGStateProperty, value);
            }
        }
        #endregion
 
        protected override void OnClosed(IServiceProvider provider)
        {
            base.RemoveProperty(ConditionedActivityGroup.CAGStateProperty);
        }
 
        #region Workflow Changes Overrides
        protected override void OnActivityChangeAdd(ActivityExecutionContext executionContext, Activity addedActivity)
        {
            if (executionContext == null)
                throw new ArgumentNullException("executionContext");
            if (addedActivity == null)
                throw new ArgumentNullException("addedActivity");
 
            if (!addedActivity.Enabled)
                return;
 
            ConditionedActivityGroup currentActivity = executionContext.Activity as ConditionedActivityGroup;
            Debug.Assert(currentActivity != null);
 
            ConditionedActivityGroupStateInfo state = currentActivity.CAGState;
            if (currentActivity.ExecutionStatus == ActivityExecutionStatus.Executing && state != null)
            {
                Debug.Assert(currentActivity == addedActivity.Parent, "Attempting to add wrong activity to CAG");
                state.ChildrenStats[addedActivity.QualifiedName] = new CAGChildStats();
            }
        }
 
        protected override void OnActivityChangeRemove(ActivityExecutionContext executionContext, Activity removedActivity)
        {
            if (executionContext == null)
                throw new ArgumentNullException("executionContext");
            if (removedActivity == null)
                throw new ArgumentNullException("removedActivity");
 
            if (!removedActivity.Enabled)
                return;
 
            ConditionedActivityGroup cag = executionContext.Activity as ConditionedActivityGroup;
            Debug.Assert(cag != null);
 
            // find out the status of the cag
 
            ConditionedActivityGroupStateInfo state = cag.CAGState;
            if ((cag.ExecutionStatus == ActivityExecutionStatus.Executing) && (state != null))
            {
                state.ChildrenStats.Remove(removedActivity.QualifiedName);
            }
        }
 
        protected override void OnWorkflowChangesCompleted(ActivityExecutionContext executionContext)
        {
            if (executionContext == null)
                throw new ArgumentNullException("executionContext");
 
            // find out the status of the cag
            ConditionedActivityGroup currentActivity = executionContext.Activity as ConditionedActivityGroup;
 
            // if CAG is executing... fire the conditions on the net result
            if (currentActivity.ExecutionStatus == ActivityExecutionStatus.Executing)
            {
                // but hold on, a derived cag could be applying model changes before it
                // "really" starts executing the activities. In that case it will evaluate
                // the conditions later, at the appropriate time.
                ConditionedActivityGroupStateInfo state = currentActivity.CAGState;
                if ((state != null) && (!state.Testing))
                {
                    // fire away...  fire away... said the CAG
                    if (this.EvaluateConditions(currentActivity, executionContext))
                    {
                        // CAG until indicates we are done, so no children execute
                        this.Cleanup(currentActivity, executionContext);
                    }
                    else
                    {
                        // start any pending activity required
                        this.TriggerChildren(currentActivity, executionContext);
                    }
                }
            }
        }
 
        #endregion
 
        #region Execution Implementation
#if	LOG
        private static void Log(string message)
        {
            Trace.WriteLine(message);
        }
#endif
 
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            if (executionContext == null)
                throw new ArgumentNullException("executionContext");
#if	LOG
            Log("Execute on " + this.QualifiedName);
#endif
 
            // go figure out what the CAG needs to do
            this.CAGState = new ConditionedActivityGroupStateInfo(this);
 
            if (EvaluateConditions(this, executionContext))
            {
                // CAG until indicates we are done, so no children execute
                return ActivityExecutionStatus.Closed;
            }
 
            // start any pending activity required
            TriggerChildren(this, executionContext);
            return this.ExecutionStatus;
        }
 
        /// <summary>
        /// Evaluate the conditions on the CAG
        /// </summary>
        /// <param name="cag"></param>
        /// <param name="context"></param>
        /// <returns>True if CAG is complete (UNTIL == true, or no UNTIL and no children execute), false otherwise</returns>
        internal bool EvaluateConditions(ConditionedActivityGroup cag, ActivityExecutionContext context)
        {
            Debug.Assert(cag != null);
            Debug.Assert(context != null);
#if	LOG
            Log("EvaluateConditions on " + cag.QualifiedName);
            cag.CAGState.DumpState("Before EvaluateConditions");
#endif
            // if we've already decided to quit this CAG, don't do anything
            if (cag.CAGState.Completed)
                return false;
 
            // if the cag has an UNTIL condition, execute it
            if ((cag.UntilCondition != null) && cag.UntilCondition.Evaluate(cag, context))
            {
                // UNTIL condition says we're done, no need to look at children
#if	LOG
                Log("Until condition is true");
#endif
                return true;
            }
 
            // until condition is false, so let's look at all children
            int childExecuting = 0; // keep track of children executing
            Dictionary<string, CAGChildStats> childrenStats = cag.CAGState.ChildrenStats;
            foreach (Activity act in cag.EnabledActivities)
            {
                // if we think the child is executing, do nothing
                if (childrenStats[act.QualifiedName].State == CAGChildState.Excuting)
                {
                    ++childExecuting;
                    continue;
                }
 
                // find the run-time activity
                Activity activity = GetRuntimeInitializedActivity(context, act);
                // should it execute?
                if (EvaluateChildConditions(cag, activity, context))
                {
                    ++childExecuting;
                    childrenStats[act.QualifiedName].State = CAGChildState.Pending;
                }
            }
#if	LOG
            cag.CAGState.DumpState("After EvaluateConditions");
#endif
 
            // if any work to do, CAG not yet done
            if (childExecuting > 0)
                return false;
 
            // CAG is quiet (nothing more to do)
            // if specified an UNTIL condition but we have nothing to do
            if (cag.UntilCondition != null)
            {
#if	LOG
                Log("CAG quiet, but UNTIL condition is false, so error time");
#endif
                throw new InvalidOperationException(SR.GetString(SR.Error_CAGQuiet, cag.QualifiedName));
            }
#if	LOG
            Log("CAG quiet");
#endif
            return true;
        }
 
        /// <summary>
        /// Evaluate the While condition for a particular child of the CAG
        /// If no While condition, it becomes "execute once"
        /// </summary>
        /// <param name="cag"></param>
        /// <param name="child"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        private bool EvaluateChildConditions(ConditionedActivityGroup cag, Activity child, ActivityExecutionContext context)
        {
#if	LOG
            Log("EvaluateChildConditions on activity " + child.QualifiedName + " inside " + cag.QualifiedName);
#endif
            // determine the result of the when condition (evaluate once if not specified)
            ConditionedActivityGroupStateInfo state = cag.CAGState;
            try
            {
                state.Testing = true;
                ActivityCondition whenCondition = (ActivityCondition)child.GetValue(ConditionedActivityGroup.WhenConditionProperty);
                return (whenCondition != null)
                    ? whenCondition.Evaluate(child, context)
                    : (state.ChildrenStats[child.QualifiedName].ExecutedCount == 0);
            }
            finally
            {
                state.Testing = false;
            }
        }
 
        /// <summary>
        /// Start any child activities that need to be run
        /// </summary>
        /// <param name="cag"></param>
        /// <param name="context"></param>
        internal void TriggerChildren(ConditionedActivityGroup cag, ActivityExecutionContext context)
        {
            Debug.Assert(cag != null);
            Debug.Assert(context != null);
#if	LOG
            Log("TriggerChildren on " + cag.QualifiedName);
            cag.CAGState.DumpState("Before TriggerChildren");
#endif
 
            Dictionary<string, CAGChildStats> childrenStats = cag.CAGState.ChildrenStats;
            // until condition is false, so let's look at all children
            foreach (Activity act in cag.EnabledActivities)
            {
                // do we think this child needs to run?
                if (childrenStats[act.QualifiedName].State != CAGChildState.Pending)
                    continue;
 
                // find the run-time activity
                Activity activity = GetRuntimeInitializedActivity(context, act);
                if (activity.ExecutionStatus == ActivityExecutionStatus.Initialized)
                    ExecuteChild(cag, activity, context);
            }
#if	LOG
            cag.CAGState.DumpState("After TriggerChildren");
#endif
        }
 
        private void ExecuteChild(ConditionedActivityGroup cag, Activity childActivity, ActivityExecutionContext context)
        {
            Debug.Assert(cag != null);
            Debug.Assert(childActivity != null);
            Debug.Assert(context != null);
            Debug.Assert(childActivity.ExecutionStatus == ActivityExecutionStatus.Initialized);
#if	LOG
            Log("ExecuteChild " + childActivity.QualifiedName + " inside " + cag.QualifiedName);
#endif
            ActivityExecutionContext childContext = GetChildExecutionContext(context, childActivity, true);
            cag.CAGState.ChildrenStats[childActivity.QualifiedName].State = CAGChildState.Excuting;
 
            // subscribe for child closure
            childContext.Activity.RegisterForStatusChange(Activity.ClosedEvent, this);
 
            // execute child in inner context
            childContext.ExecuteActivity(childContext.Activity);
        }
 
        protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext)
        {
            if (executionContext == null)
                throw new ArgumentNullException("executionContext");
 
            // child activities are cancelled and could complete asynchronously.
            // if there was no asynchronous stuff then lets do the cag level cleanup
 
            // if we are already done (or never started), then we are already closed
            if (this.CAGState == null)
                return ActivityExecutionStatus.Closed;
 
            return Cleanup(this, executionContext) ? ActivityExecutionStatus.Closed : ActivityExecutionStatus.Canceling;
        }
 
        #region IActivityEventListener<ActivityExecutionStatusChangedEventArgs> Members
        void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent(object sender, ActivityExecutionStatusChangedEventArgs e)
        {
            this.HandleEvent(sender as ActivityExecutionContext, new SubscriptionEventArg(e, EventType.StatusChange));
        }
        #endregion
 
        internal void HandleEvent(ActivityExecutionContext context, SubscriptionEventArg e)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (e == null)
                throw new ArgumentNullException("e");
 
            ConditionedActivityGroup cag = context.Activity as ConditionedActivityGroup;
            if (cag == null)
                throw new ArgumentException(SR.GetString(SR.Error_InvalidCAGActivityType), "activity");
 
            // Already done the cleanup from another child's signalling
            if (cag.ExecutionStatus == ActivityExecutionStatus.Closed)
                return;
 
            if (e.SubscriptionType != EventType.StatusChange)
            {
                // split into seperate test to keep FxCop happy (only place SubscriptionType used)
                Debug.Assert(false, "This CAG activity handler does not handle this event");
            }
            ActivityExecutionStatusChangedEventArgs args1 = (ActivityExecutionStatusChangedEventArgs)e.Args;
#if	LOG
            Log("HandleEvent for " + cag.QualifiedName);
            Log("event = " + e.ToString());
            Log("activity = " + args1.Activity.QualifiedName);
#endif
 
            bool timeToQuit = false;
 
            // is this event is for an immediate child?
            Debug.Assert(cag == args1.Activity.Parent, "Received event for non-child of CAG");
            Dictionary<string, CAGChildStats> childrenStats = cag.CAGState.ChildrenStats;
 
            // it is possible that dynamic update has removed the child before we get the closed event
            // if that is the case, we don't need to update it's stats since it's not there
            if (childrenStats.ContainsKey(args1.Activity.QualifiedName))
            {
                // update our state about the child
                if (args1.ExecutionStatus != ActivityExecutionStatus.Executing)
                    childrenStats[args1.Activity.QualifiedName].State = CAGChildState.Idle;
 
                // @undone: this will break if scopes move to "Delayed" closing after Completed.
                if (args1.ExecutionStatus == ActivityExecutionStatus.Closed)
                    childrenStats[args1.Activity.QualifiedName].ExecutedCount++;
 
                try
                {
                    // re-evaluate the conditions on any status change, as long as the CAG is still executing
                    if (cag.ExecutionStatus == ActivityExecutionStatus.Executing)
                        timeToQuit = EvaluateConditions(cag, context);
                }
                finally
                {
                    // get rid of the child that just completed
                    // do this in the finally so that the child is cleaned up, 
                    // even if EvaluateConditions throws beause the CAG is stalled
                    CleanupChildAtClosure(context, args1.Activity);
                }
            }
            else
            {
                // child has been removed
                // we still need to see if the CAG is done, provided we are still executing
                if (cag.ExecutionStatus == ActivityExecutionStatus.Executing)
                    timeToQuit = EvaluateConditions(cag, context);
            }
 
            // is the CAG just completed?
            if (timeToQuit)
            {
                Cleanup(cag, context);
            }
            else if (cag.CAGState.Completed)
            {
                // if the CAG is simply waiting for all children to complete, see if this is the last one
                if (AllChildrenQuiet(cag, context))
                {
                    // Mark the CAG as closed, if it hasn't already been marked so.
                    context.CloseActivity();
                }
            }
            else
            {
                // CAG not done, so see if any children need to start
                TriggerChildren(cag, context);
            }
        }
 
        internal bool Cleanup(ConditionedActivityGroup cag, ActivityExecutionContext context)
        {
            // the completion condition has fired, or we are canceling
            // either way, we want to cleanup
            ConditionedActivityGroupStateInfo state = cag.CAGState;
            state.Completed = true;
 
            // cancel any children currently running
            bool childrenActive = false;
            Dictionary<string, CAGChildStats> childrenStats = state.ChildrenStats;
            foreach (Activity act in cag.EnabledActivities)
            {
                // reset any Pending Execution for all child activity
                if (childrenStats[act.QualifiedName].State == CAGChildState.Pending)
                    childrenStats[act.QualifiedName].State = CAGChildState.Idle;
 
                // find the run-time activity
                ActivityExecutionContext childContext = GetChildExecutionContext(context, act, false);
                if (childContext != null)
                {
                    // child must be running somewhere
                    Activity activity = GetRuntimeInitializedActivity(context, act);
                    switch (activity.ExecutionStatus)
                    {
                        case ActivityExecutionStatus.Executing:
                            // schedule cancellation on child
                            childContext.CancelActivity(activity);
                            childrenActive = true;
                            break;
 
                        case ActivityExecutionStatus.Canceling:
                        case ActivityExecutionStatus.Faulting:
                            childrenActive = true;
                            break;
 
                        case ActivityExecutionStatus.Closed:
                            CleanupChildAtClosure(context, activity);
                            break;
                        default:
                            // unhook our handler
                            // others will be removed when we get the complete/cancel notification
                            act.UnregisterForStatusChange(Activity.ClosedEvent, this);
                            break;
                    }
                }
            }
 
            // if the CAG is quiet, we are all done
            if (!childrenActive)
                context.CloseActivity();
            return !childrenActive;
        }
 
        private void CleanupChildAtClosure(ActivityExecutionContext context, Activity childActivity)
        {
            Debug.Assert(context != null);
            Debug.Assert(childActivity != null);
            Debug.Assert(childActivity.ExecutionStatus == ActivityExecutionStatus.Closed);
 
            //UnSubsribe child closure of completed activity.
            childActivity.UnregisterForStatusChange(Activity.ClosedEvent, this);
 
            //Dispose the execution context;
            ActivityExecutionContext childContext = GetChildExecutionContext(context, childActivity, false);
            ActivityExecutionContextManager contextManager = context.ExecutionContextManager;
            contextManager.CompleteExecutionContext(childContext);
        }
 
        private Activity GetRuntimeInitializedActivity(ActivityExecutionContext context, Activity childActivity)
        {
            ActivityExecutionContext childContext = GetChildExecutionContext(context, childActivity, false);
 
            if (childContext == null)
                return childActivity;
 
            return childContext.Activity;
        }
 
        private static ActivityExecutionContext GetChildExecutionContext(ActivityExecutionContext context, Activity childActivity, bool createIfNotExists)
        {
            ActivityExecutionContextManager contextManager = context.ExecutionContextManager;
            ActivityExecutionContext childContext = contextManager.GetExecutionContext(childActivity);
            if (childContext != null)
                return childContext;
 
            if (createIfNotExists)
                childContext = contextManager.CreateExecutionContext(childActivity);
 
            return childContext;
        }
 
        bool AllChildrenQuiet(ConditionedActivityGroup cag, ActivityExecutionContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
 
            // if there are any execution contexts, 1 or more children still doing something
            foreach (ActivityExecutionContext activeContext in context.ExecutionContextManager.ExecutionContexts)
            {
                if (cag.GetActivityByName(activeContext.Activity.QualifiedName, true) != null)
                {
                    return false;
                }
            }
 
            // no children
            return true;
        }
    }
 
    internal sealed class SubscriptionEventArg : EventArgs
    {
        private EventArgs _args;
        private EventType _subscriptionType;
 
        internal EventArgs Args
        {
            get { return _args; }
        }
 
        internal EventType SubscriptionType
        {
            get { return _subscriptionType; }
        }
 
        public override string ToString()
        {
            return "SubscriptionEventArg(" + (_args == null ? "null" : _args.ToString()) + ")";
        }
 
        internal SubscriptionEventArg(EventArgs args, EventType subType)
        {
            _args = args;
            _subscriptionType = subType;
        }
    }
 
    [Serializable]
    internal enum EventType : byte
    {
        Timer = 0,
        DataChange = 1,
        StatusChange = 2,
        MessageArrival = 3,
        LockAcquisition = 4,
        InterActivity = 6,
    }
 
    #region ConditionedActivityGroupStateInfo
    [Serializable]
    internal sealed class ConditionedActivityGroupStateInfo
    {
        private bool completed;
        private bool testing;
        private Dictionary<string, CAGChildStats> childActivityStats;
 
        #region Accessors
 
        internal bool Completed
        {
            get { return this.completed; }
            set { this.completed = value; }
        }
 
        internal bool Testing
        {
            get { return testing; }
            set { testing = value; }
        }
 
        internal Dictionary<string, CAGChildStats> ChildrenStats
        {
            get { return this.childActivityStats; }
        }
 
        #endregion Accessors
 
        internal ConditionedActivityGroupStateInfo(ConditionedActivityGroup cag)
        {
            int len = cag.EnabledActivities.Count;
            this.childActivityStats = new Dictionary<string, CAGChildStats>(len);
            foreach (Activity act in cag.EnabledActivities)
                this.childActivityStats[act.QualifiedName] = new CAGChildStats();
        }
 
#if	LOG
        internal void DumpState(string message)
        {
            Trace.WriteLine(message + " completed = " + Completed.ToString());
            foreach (string key in this.childActivityStats.Keys)
            {
                Trace.WriteLine(key + ": state = " + this.childActivityStats[key].State + ", performed = " + this.childActivityStats[key].ExecutedCount);
            }
        }
#endif
 
    }
 
    [Serializable]
    internal enum CAGChildState : byte
    {
        Idle,
        Pending,
        Excuting
    }
 
    [Serializable]
    internal class CAGChildStats
    {
        internal int ExecutedCount = 0;
        internal CAGChildState State = CAGChildState.Idle;
        internal CAGChildStats()
        { }
    }
    #endregion
 
        #endregion
 
 
    #region Validator
    internal sealed class ConditionedActivityGroupValidator : CompositeActivityValidator
    {
        public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
        {
            ValidationErrorCollection validationErrors = base.Validate(manager, obj);
 
            ConditionedActivityGroup conditionedActivityGroup = obj as ConditionedActivityGroup;
            if (conditionedActivityGroup == null)
                throw new ArgumentException(SR.GetString(SR.Error_UnexpectedArgumentType, typeof(ConditionedActivityGroup).FullName), "obj");
 
            return validationErrors;
        }
 
        public override ValidationError ValidateActivityChange(Activity activity, ActivityChangeAction action)
        {
            if (activity == null)
                throw new ArgumentNullException("activity");
            if (action == null)
                throw new ArgumentNullException("action");
 
            if (activity.ExecutionStatus != ActivityExecutionStatus.Initialized &&
                activity.ExecutionStatus != ActivityExecutionStatus.Executing &&
                activity.ExecutionStatus != ActivityExecutionStatus.Closed)
            {
                return new ValidationError(SR.GetString(SR.Error_DynamicActivity2, activity.QualifiedName, activity.ExecutionStatus, activity.GetType().FullName), ErrorNumbers.Error_DynamicActivity2);
            }
 
            // if we are currently executing, make sure that we are not changing something already running
            // removed since changes mean that the child activity is going to get validated anyway
 
            return null;
        }
    }
    #endregion
}