File: AuthoringOM\Serializer\ActivitySurrogate.cs
Project: ndp\cdf\src\WF\Common\System.Workflow.ComponentModel.csproj (System.Workflow.ComponentModel)
namespace System.Workflow.ComponentModel.Serialization
{
    using System;
    using System.IO;
    using System.Xml;
    using System.Reflection;
    using System.Collections;
    using System.Diagnostics;
    using System.Globalization;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
 
    internal sealed class ActivitySurrogate : ISerializationSurrogate
    {
        public ActivitySurrogate()
        {
        }
        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            if (Activity.ContextIdToActivityMap == null)
                throw new InvalidOperationException(SR.GetString(SR.Error_ActivitySaveLoadNotCalled));
 
            Activity activity = (Activity)obj;
            bool isSurroundingActivity = false;
            bool isDanglingActivity = IsDanglingActivity(activity, out isSurroundingActivity);
            if (isSurroundingActivity)
            {
                // if this object is in parent chain then replace it with token
                if (activity.ContextActivity != null)
                    info.AddValue("cid", activity.ContextId);
 
                info.AddValue("id", activity.DottedPath);
                info.SetType(typeof(ActivityRef));
            }
            else if (!isDanglingActivity)
            {
                info.AddValue("id", activity.DottedPath);
 
                string[] names = null;
                MemberInfo[] members = FormatterServicesNoSerializableCheck.GetSerializableMembers(obj.GetType(), out names);
                object[] memberDatas = FormatterServices.GetObjectData(obj, members);
                // To improve performance, we specialize the case where there are only 2 fields.  One is the 
                // instance dependency property values dictionary and the other is the "disposed" event.
                if (memberDatas != null && memberDatas.Length == 2)
                {
                    Debug.Assert(members[0].Name == "dependencyPropertyValues" && members[1].Name == "disposed");
                    IDictionary<DependencyProperty, object> instanceProperties = (IDictionary<DependencyProperty, object>)memberDatas[0];
                    if (instanceProperties != null && instanceProperties.Count > 0)
                    {
                        foreach (KeyValuePair<DependencyProperty, object> kvp in instanceProperties)
                        {
                            if (kvp.Key != null && !kvp.Key.DefaultMetadata.IsNonSerialized)
                            {
                                info.AddValue("memberData", memberDatas[0]);
                                break;
                            }
                        }
                    }
                    if (memberDatas[1] != null)
                        info.AddValue("disposed", memberDatas[1]);
                }
                else
                {
                    info.AddValue("memberNames", names);
                    info.AddValue("memberDatas", memberDatas);
                }
 
                // for root activity serialize the change actions if there are any
                if (obj is Activity && ((Activity)obj).Parent == null)
                {
                    string wMarkup = activity.GetValue(Activity.WorkflowXamlMarkupProperty) as string;
                    if (!string.IsNullOrEmpty(wMarkup))
                    {
                        info.AddValue("workflowMarkup", wMarkup);
 
                        //if we got rules in XAML Load case, serialize them as well
                        string rMarkup = activity.GetValue(Activity.WorkflowRulesMarkupProperty) as string;
                        if (!string.IsNullOrEmpty(rMarkup))
                            info.AddValue("rulesMarkup", rMarkup);
                    }
                    else
                        info.AddValue("type", activity.GetType());
 
                    Activity workflowDefinition = (Activity)activity.GetValue(Activity.WorkflowDefinitionProperty);
                    if (workflowDefinition != null)
                    {
                        ArrayList changeActions = (ArrayList)workflowDefinition.GetValue(WorkflowChanges.WorkflowChangeActionsProperty);
                        if (changeActions != null)
                        {
                            Guid changeVersion = (Guid)workflowDefinition.GetValue(WorkflowChanges.WorkflowChangeVersionProperty);
                            info.AddValue("workflowChangeVersion", changeVersion);
                            using (StringWriter changeActionsStringWriter = new StringWriter(CultureInfo.InvariantCulture))
                            {
                                using (XmlWriter xmlWriter = Design.Helpers.CreateXmlWriter(changeActionsStringWriter))
                                {
                                    new WorkflowMarkupSerializer().Serialize(xmlWriter, changeActions);
                                    info.AddValue("workflowChanges", changeActionsStringWriter.ToString());
                                }
                            }
                        }
                    }
                }
                info.SetType(typeof(ActivitySerializedRef));
            }
            else
            {
                info.AddValue("id", activity.Name);
                info.AddValue("type", activity.GetType());
                info.SetType(typeof(DanglingActivityRef));
            }
        }
        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            return null;
        }
        private bool IsDanglingActivity(Activity activity, out bool isSurrounding)
        {
            isSurrounding = false;
            bool isDangling = false;
            do
            {
                if (Activity.ActivityRoots.Contains(activity))
                {
                    isDangling = false;
                    break;
                }
 
                if (activity.Parent == null)
                {
                    isDangling = ((Activity)Activity.ActivityRoots[0]).RootActivity != activity;
                    break;
                }
 
                if (!activity.Parent.Activities.Contains(activity))
                {
                    IList<Activity> activeContextActivities = null;
                    if (activity.Parent.ContextActivity != null)
                        activeContextActivities = (IList<Activity>)activity.Parent.ContextActivity.GetValue(Activity.ActiveExecutionContextsProperty);
 
                    if (activeContextActivities == null || !activeContextActivities.Contains(activity))
                    {
                        isDangling = true;
                        break;
                    }
                }
                activity = activity.Parent;
            } while (activity != null);
 
            isSurrounding = (!isDangling && !Activity.ActivityRoots.Contains(activity));
            return isDangling;
        }
 
        [Serializable]
        private sealed class ActivityRef : IObjectReference
        {
            [OptionalField]
            private int cid = 0;
            private string id = string.Empty;
 
            Object IObjectReference.GetRealObject(StreamingContext context)
            {
                if (Activity.ContextIdToActivityMap == null)
                    throw new InvalidOperationException(SR.GetString(SR.Error_ActivitySaveLoadNotCalled));
 
                Activity contextActivity = (Activity)Activity.ContextIdToActivityMap[this.cid];
                return contextActivity.TraverseDottedPathFromRoot(this.id);
            }
        }
 
        [Serializable]
        private sealed class ActivitySerializedRef : IObjectReference, IDeserializationCallback
        {
            private string id = string.Empty;
 
            [OptionalField]
            private object memberData = null;
 
            [OptionalField]
            private object[] memberDatas = null;
 
            [OptionalField]
            private string[] memberNames = null;
 
            [OptionalField]
            private Type type = null;
 
            [OptionalField]
            private string workflowMarkup = null;
 
            [OptionalField]
            private string rulesMarkup = null;
 
            [OptionalField]
            private string workflowChanges = null;
 
            [OptionalField]
            private Guid workflowChangeVersion = Guid.Empty;
 
            [OptionalField]
            private EventHandler disposed = null;
 
            [NonSerialized]
            private Activity cachedDefinitionActivity = null;
 
            [NonSerialized]
            private Activity cachedActivity = null;
 
            [NonSerialized]
            private int lastPosition = 0;
 
            object IObjectReference.GetRealObject(StreamingContext context)
            {
                // if definition activity has not been yet deserialized then return null
                if (Activity.DefinitionActivity == null)
                {
                    if (this.type == null && string.IsNullOrEmpty(this.workflowMarkup))
                        return null;
 
                    Activity rootActivityDefinition = null;
                    // We always call into runtime to resolve an activity.  The runtime may return an existing cached workflow definition
                    // or it may return a new one if none exists.
                    // When we have dynamic updates, we ask runtime to always return us a new instance of the workflow definition.
                    // This new instance should not be initialized for runtime.  We must apply workflow changes first
                    // before we initialize it for runtime.
                    bool createNewDef = (this.workflowChanges != null);
                    rootActivityDefinition = Activity.OnResolveActivityDefinition(this.type, this.workflowMarkup, this.rulesMarkup, createNewDef, !createNewDef, null);
                    if (rootActivityDefinition == null)
                        throw new NullReferenceException(SR.GetString(SR.Error_InvalidRootForWorkflowChanges));
 
                    if (createNewDef)
                    {
                        ArrayList changeActions = Activity.OnResolveWorkflowChangeActions(this.workflowChanges, rootActivityDefinition);
                        foreach (WorkflowChangeAction changeAction in changeActions)
                        {
                            bool result = changeAction.ApplyTo(rootActivityDefinition);
                            Debug.Assert(result, "ApplyTo failed");
                        }
 
                        rootActivityDefinition.SetValue(WorkflowChanges.WorkflowChangeActionsProperty, changeActions);
                        rootActivityDefinition.SetValue(WorkflowChanges.WorkflowChangeVersionProperty, this.workflowChangeVersion);
                        ((IDependencyObjectAccessor)rootActivityDefinition).InitializeDefinitionForRuntime(null);
                    }
 
                    // assign it over to the thread static guy so others can access it as well.
                    Activity.DefinitionActivity = rootActivityDefinition;
                }
 
                if (this.cachedActivity == null)
                {
                    this.cachedDefinitionActivity = Activity.DefinitionActivity.TraverseDottedPathFromRoot(this.id);
                    this.cachedActivity = (Activity)FormatterServices.GetUninitializedObject(this.cachedDefinitionActivity.GetType());
                }
                return this.cachedActivity;
            }
            void IDeserializationCallback.OnDeserialization(object sender)
            {
                if (this.cachedActivity != null)
                {
                    bool done = false;
                    string[] currentMemberNames = null;
                    MemberInfo[] members = FormatterServicesNoSerializableCheck.GetSerializableMembers(this.cachedActivity.GetType(), out currentMemberNames);
                    if (members.Length == 2)
                    {
                        Debug.Assert(members[0].Name == "dependencyPropertyValues" && members[1].Name == "disposed");
                        // To improve performance, we specialize the case where there are only 2 fields.  One is the 
                        // instance dependency property values dictionary and the other is the "disposed" event.
                        if (this.memberData != null && this.disposed != null)
                        {
                            FormatterServices.PopulateObjectMembers(this.cachedActivity, members, new object[] { this.memberData, this.disposed });
                            done = true;
                        }
                        else if (this.memberData != null)
                        {
                            FormatterServices.PopulateObjectMembers(this.cachedActivity, new MemberInfo[] { members[0] }, new object[] { this.memberData });
                            done = true;
                        }
                        else if (this.disposed != null)
                        {
                            FormatterServices.PopulateObjectMembers(this.cachedActivity, new MemberInfo[] { members[1] }, new object[] { this.disposed });
                            done = true;
                        }
                    }
 
                    if (!done && this.memberDatas != null)
                    {
                        // re-order the member datas if needed
                        Object[] currentMemberDatas = new object[members.Length];
                        for (int index = 0; index < currentMemberNames.Length; index++)
                            currentMemberDatas[index] = this.memberDatas[Position(currentMemberNames[index])];
 
                        // populate the object
                        FormatterServices.PopulateObjectMembers(this.cachedActivity, members, currentMemberDatas);
                    }
                    this.cachedActivity.FixUpMetaProperties(this.cachedDefinitionActivity);
                    this.cachedActivity = null;
                }
            }
            private int Position(String name)
            {
                if (this.memberNames.Length > 0 && this.memberNames[this.lastPosition].Equals(name))
                {
                    return this.lastPosition;
                }
                else if ((++this.lastPosition < this.memberNames.Length) && (this.memberNames[this.lastPosition].Equals(name)))
                {
                    return this.lastPosition;
                }
                else
                {
                    // Search for name
                    for (int i = 0; i < this.memberNames.Length; i++)
                    {
                        if (this.memberNames[i].Equals(name))
                        {
                            this.lastPosition = i;
                            return this.lastPosition;
                        }
                    }
 
                    //throw new SerializationException(String.Format(Environment.GetResourceString("Serialization_MissingMember"),name,objectType));
                    this.lastPosition = 0;
                    return -1;
                }
            }
        }
        [Serializable]
        private class DanglingActivityRef : IObjectReference
        {
            private string id = string.Empty;
            private Type type = null;
 
            [NonSerialized]
            private Activity activity = null;
 
            object IObjectReference.GetRealObject(StreamingContext context)
            {
                if (this.activity == null)
                {
                    // Make sure this.type derives from Activity
                    if (typeof(Activity).IsAssignableFrom(this.type))
                    {
                        // meta properties and other instance properties, parent-child relation ships are lost
                        this.activity = (Activity)Activator.CreateInstance(this.type);
                        this.activity.Name = this.id;
                    }
                    else
                    // Before the check for typeof(Activity).IsAssignableFrom, the object was being created and if it
                    // was not an Activity, an InvalidCastException would be thrown. For compatibility, continuing with that
                    // exception, but not create an instance of the type.
                    {
                        throw new InvalidCastException();
                    }
                }
                return this.activity;
            }
        }
    }
}