File: System\Activities\XamlIntegration\ActivityBuilderXamlWriter.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------
 
namespace System.Activities.XamlIntegration
{
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Runtime;
    using System.Windows.Markup;
    using System.Xaml;
    using System.Xaml.Schema;
    using System.Xml;
 
    // This class rewrites an <ActivityBuilder to <Activity x:Class
    // ActivityBuilder.Properties is rewritten to x:Members
    // ActivityBuilder.Name is rewritten as x:Class
    // ActivityBuilder.Implementation is rewritten as Activity.Implementation
    // 
    // Because of our [DependsOn] annotations, Name is followed by Attributes, Properties,
    // Constraints and, lastly, Implementation. The first few relationships are assumed
    // and enforced through our state machine here to avoid buffering the whole node stream
    // in common cases (such as no attributes specified).
    class ActivityBuilderXamlWriter : XamlWriter
    {
        readonly XamlWriter innerWriter;
 
        // These may be a closed generic type in the Activity<T> case (or null if not an ActivityBuilder),
        // so we need to compute this value dynamically
        XamlType activityBuilderXamlType;
        XamlType activityXamlType;
 
        XamlType activityPropertyXamlType;
        XamlType xamlTypeXamlType;
        XamlType typeXamlType;
        XamlType activityPropertyReferenceXamlType;
 
        XamlMember activityPropertyType;
        XamlMember activityPropertyName;
        XamlMember activityPropertyValue;
        XamlMember activityBuilderName;
        XamlMember activityBuilderAttributes;
        XamlMember activityBuilderProperties;
        XamlMember activityBuilderPropertyReference;
        XamlMember activityBuilderPropertyReferences;
 
        bool notRewriting;
        int currentDepth;
 
        // we need to accrue namespace so that we can resolve DynamicActivityProperty.Type
        // and correctly strip superfluous wrapper nodes around default values
        NamespaceTable namespaceTable;
        BuilderXamlNode currentState;
        Stack<BuilderXamlNode> pendingStates;
 
        public ActivityBuilderXamlWriter(XamlWriter innerWriter)
            : base()
        {
            this.innerWriter = innerWriter;
            this.currentState = new RootNode(this);
            this.namespaceTable = new NamespaceTable();
        }
 
        public override XamlSchemaContext SchemaContext
        {
            get
            {
                return this.innerWriter.SchemaContext;
            }
        }
 
        void SetActivityType(XamlType activityXamlType, XamlType activityBuilderXamlType)
        {
            if (activityXamlType == null)
            {
                this.notRewriting = true;
            }
            else
            {
                this.activityXamlType = activityXamlType;
                this.activityBuilderXamlType = activityBuilderXamlType;
                this.xamlTypeXamlType = this.SchemaContext.GetXamlType(typeof(XamlType));
                this.typeXamlType = this.SchemaContext.GetXamlType(typeof(Type));
 
                this.activityPropertyXamlType = this.SchemaContext.GetXamlType(typeof(DynamicActivityProperty));
                this.activityPropertyType = this.activityPropertyXamlType.GetMember("Type");
                this.activityPropertyName = this.activityPropertyXamlType.GetMember("Name");
                this.activityPropertyValue = this.activityPropertyXamlType.GetMember("Value");
 
                this.activityBuilderName = this.activityBuilderXamlType.GetMember("Name");
                this.activityBuilderAttributes = this.activityBuilderXamlType.GetMember("Attributes");
                this.activityBuilderProperties = this.activityBuilderXamlType.GetMember("Properties");
                this.activityBuilderPropertyReference = this.SchemaContext.GetXamlType(typeof(ActivityBuilder)).GetAttachableMember("PropertyReference");
                this.activityBuilderPropertyReferences = this.SchemaContext.GetXamlType(typeof(ActivityBuilder)).GetAttachableMember("PropertyReferences");
                this.activityPropertyReferenceXamlType = this.SchemaContext.GetXamlType(typeof(ActivityPropertyReference));
            }
        }
 
        public override void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
        {
            if (this.notRewriting)
            {
                this.innerWriter.WriteNamespace(namespaceDeclaration);
                return;
            }
 
            if (this.namespaceTable != null)
            {
                this.namespaceTable.AddNamespace(namespaceDeclaration);
            }
            this.currentState.WriteNamespace(namespaceDeclaration);
        }
 
        public override void WriteValue(object value)
        {
            if (this.notRewriting)
            {
                this.innerWriter.WriteValue(value);
                return;
            }
 
            this.currentState.WriteValue(value);
        }
 
        public override void WriteStartObject(XamlType xamlType)
        {
            if (this.notRewriting)
            {
                this.innerWriter.WriteStartObject(xamlType);
                return;
            }
 
            EnterDepth();
            this.currentState.WriteStartObject(xamlType);
        }
 
        public override void WriteGetObject()
        {
            if (this.notRewriting)
            {
                this.innerWriter.WriteGetObject();
                return;
            }
 
            EnterDepth();
            this.currentState.WriteGetObject();
        }
 
        public override void WriteEndObject()
        {
            if (this.notRewriting)
            {
                this.innerWriter.WriteEndObject();
                return;
            }
 
            this.currentState.WriteEndObject();
            ExitDepth();
        }
 
        public override void WriteStartMember(XamlMember xamlMember)
        {
            if (this.notRewriting)
            {
                this.innerWriter.WriteStartMember(xamlMember);
                return;
            }
 
            EnterDepth();
            this.currentState.WriteStartMember(xamlMember);
        }
 
        public override void WriteEndMember()
        {
            if (this.notRewriting)
            {
                this.innerWriter.WriteEndMember();
                return;
            }
 
            this.currentState.WriteEndMember();
            ExitDepth();
        }
 
        void PushState(BuilderXamlNode state)
        {
            if (this.pendingStates == null)
            {
                this.pendingStates = new Stack<BuilderXamlNode>();
            }
            this.pendingStates.Push(this.currentState);
            this.currentState = state;
        }
 
        void EnterDepth()
        {
            Fx.Assert(!this.notRewriting, "we only use depth calculation if we're rewriting");
            this.currentDepth++;
            if (this.namespaceTable != null)
            {
                this.namespaceTable.EnterScope();
            }
        }
 
        void ExitDepth()
        {
            Fx.Assert(!this.notRewriting, "we only use depth calculation if we're rewriting");
            if (this.currentState.Depth == this.currentDepth)
            {
                // complete the current state
                this.currentState.Complete();
 
                // and pop off the next state to look for
                if (this.pendingStates.Count > 0)
                {
                    this.currentState = this.pendingStates.Pop();
                }
            }
            this.currentDepth--;
            if (this.namespaceTable != null)
            {
                this.namespaceTable.ExitScope();
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (disposing)
            {
                ((IDisposable)this.innerWriter).Dispose();
            }
        }
 
        abstract class BuilderXamlNode
        {
            protected BuilderXamlNode(ActivityBuilderXamlWriter writer)
            {
                this.Depth = writer.currentDepth;
                this.Writer = writer;
                this.CurrentWriter = writer.innerWriter;
            }
 
            public int Depth
            {
                get;
                private set;
            }
 
            // a lot of nodes just redirect output, this
            // allows them to avoid overriding everything just for that
            public XamlWriter CurrentWriter
            {
                get;
                protected set;
            }
 
            protected ActivityBuilderXamlWriter Writer
            {
                get;
                private set;
            }
 
            protected internal virtual void Complete()
            {
            }
 
            protected internal virtual void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
            {
                CurrentWriter.WriteNamespace(namespaceDeclaration);
            }
 
            protected internal virtual void WriteStartObject(XamlType xamlType)
            {
                CurrentWriter.WriteStartObject(xamlType);
            }
 
            protected internal virtual void WriteGetObject()
            {
                CurrentWriter.WriteGetObject();
            }
 
            protected internal virtual void WriteEndObject()
            {
                CurrentWriter.WriteEndObject();
            }
 
            protected internal virtual void WriteStartMember(XamlMember xamlMember)
            {
                CurrentWriter.WriteStartMember(xamlMember);
            }
 
            protected internal virtual void WriteEndMember()
            {
                CurrentWriter.WriteEndMember();
            }
 
            protected internal virtual void WriteValue(object value)
            {
                CurrentWriter.WriteValue(value);
            }
        }
 
        // RootNode needs to buffer nodes until we finish processing Name + Properties
        // because we need to insert our namespace _before_ the first StartObject.
        // this is the starting value for ActivityBuilderXamlWriter.currentNode
        class RootNode : BuilderXamlNode
        {
            const string PreferredXamlNamespaceAlias = "x";
            const string PreferredClassAlias = "this";
 
            bool wroteXamlNamespace;
            HashSet<string> rootLevelPrefixes;
 
            XamlNodeQueue pendingNodes;
 
            public RootNode(ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.pendingNodes = new XamlNodeQueue(writer.SchemaContext);
                base.CurrentWriter = this.pendingNodes.Writer;
            }
 
            protected internal override void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
            {
                if (Writer.currentDepth == 0 && !this.wroteXamlNamespace)
                {
                    if (namespaceDeclaration.Namespace == XamlLanguage.Xaml2006Namespace)
                    {
                        this.wroteXamlNamespace = true;
                    }
                    else
                    {
                        if (this.rootLevelPrefixes == null)
                        {
                            this.rootLevelPrefixes = new HashSet<string>();
                        }
                        this.rootLevelPrefixes.Add(namespaceDeclaration.Prefix);
                    }
                }
                base.WriteNamespace(namespaceDeclaration);
            }
 
            protected internal override void WriteStartObject(XamlType xamlType)
            {
                if (Writer.currentDepth == 1)
                {
                    XamlType activityXamlType = null;
 
                    // root object: see if we're serializing an ActivityBuilder
                    if (xamlType.UnderlyingType == typeof(ActivityBuilder))
                    {
                        activityXamlType = Writer.SchemaContext.GetXamlType(typeof(Activity));
                    }
                    // or an ActivityBuilder<TResult>
                    else if (xamlType.IsGeneric && xamlType.UnderlyingType != null
                        && xamlType.UnderlyingType.GetGenericTypeDefinition() == typeof(ActivityBuilder<>))
                    {
                        Type activityType = xamlType.TypeArguments[0].UnderlyingType;
                        activityXamlType = Writer.SchemaContext.GetXamlType(typeof(Activity<>).MakeGenericType(activityType));
                    }
 
                    Writer.SetActivityType(activityXamlType, xamlType);
 
                    if (activityXamlType != null)
                    {
                        Writer.PushState(new BuilderClassNode(this, Writer));
                        return;
                    }
                    else
                    {
                        // we should be a pass through. Flush any buffered nodes and get out of the way
                        FlushPendingNodes(null);
                    }
                }
                base.WriteStartObject(xamlType);
            }
 
            public void FlushPendingNodes(string classNamespace)
            {
                base.CurrentWriter = this.Writer.innerWriter;
                if (!Writer.notRewriting)
                {
                    // make sure we have any required namespaces
                    if (!this.wroteXamlNamespace)
                    {
                        string xamlNamespaceAlias = GenerateNamespacePrefix(PreferredXamlNamespaceAlias);
                        this.WriteNamespace(new NamespaceDeclaration(XamlLanguage.Xaml2006Namespace, xamlNamespaceAlias));
                    }
 
                    // If there's an x:Class="Foo.Bar", add a namespace declaration for Foo in the local assembly so we can 
                    // say stuff like this:Bar.MyProperty later on. DON'T add the namespace declaration if somebody has already 
                    // declared the namespace in the nodestream though (duplicates are an error).
                    if (classNamespace != null)
                    {
                        bool sawClassNamespace = false;
 
                        XamlReader reader = this.pendingNodes.Reader;
                        XamlWriter writer = this.Writer.innerWriter;
                        while (reader.Read() && reader.NodeType == XamlNodeType.NamespaceDeclaration)
                        {
                            if (classNamespace.Equals(reader.Namespace.Namespace))
                            {
                                sawClassNamespace = true;
                            }
                            writer.WriteNode(reader);
                        }
 
                        if (!sawClassNamespace)
                        {
                            string classNamespaceAlias = GenerateNamespacePrefix(PreferredClassAlias);
                            writer.WriteNamespace(new NamespaceDeclaration(classNamespace, classNamespaceAlias));
                        }
 
                        // We may have consumed the first non-namespace node off the reader in order 
                        // to check it for being a NamespaceDeclaration. Make sure it still gets written.
                        if (!reader.IsEof)
                        {
                            writer.WriteNode(reader);
                        }
                    }
 
                    this.rootLevelPrefixes = null; // not needed anymore
                }
 
                XamlServices.Transform(this.pendingNodes.Reader, this.Writer.innerWriter, false);
                this.pendingNodes = null;
            }
 
            string GenerateNamespacePrefix(string desiredPrefix)
            {
                string aliasPostfix = string.Empty;
                // try postfixing 1-1000 first
                for (int i = 1; i <= 1000; i++)
                {
                    string alias = desiredPrefix + aliasPostfix;
                    if (!this.rootLevelPrefixes.Contains(alias))
                    {
                        return alias;
                    }
                    aliasPostfix = i.ToString(CultureInfo.InvariantCulture);
                }
 
                // fall back to GUID
                return desiredPrefix + Guid.NewGuid().ToString();
            }
        }
 
        // <ActivityBuilder>...</ActivityBuilder>
        class BuilderClassNode : BuilderXamlNode
        {
            RootNode rootNode;
 
            string xClassNamespace;
            XamlType xClassXamlType;
            XamlNodeQueue xClassNodes;
            XamlNodeQueue xClassAttributeNodes;
            XamlNodeQueue xPropertiesNodes;
            XamlNodeQueue otherNodes;
            List<KeyValuePair<string, XamlNodeQueue>> defaultValueNodes;
 
            public BuilderClassNode(RootNode rootNode, ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.rootNode = rootNode;
 
                // by default, if we're not in a special sub-tree, ferret the nodes away on the side
                this.otherNodes = new XamlNodeQueue(writer.SchemaContext);
                base.CurrentWriter = this.otherNodes.Writer;
            }
 
            public void SetXClass(string builderName, XamlNodeQueue nameNodes)
            {
                this.xClassNodes = new XamlNodeQueue(Writer.SchemaContext);
                this.xClassNodes.Writer.WriteStartMember(XamlLanguage.Class);
                this.xClassNamespace = null;
                string xClassName = builderName;
                if (string.IsNullOrEmpty(xClassName))
                {
                    xClassName = string.Format(CultureInfo.CurrentCulture, "_{0}", Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 4));
                }
 
                if (nameNodes != null)
                {
                    XamlServices.Transform(nameNodes.Reader, this.xClassNodes.Writer, false);
                }
                else
                {
                    this.xClassNodes.Writer.WriteValue(xClassName);
                    this.xClassNodes.Writer.WriteEndMember();
                }
 
                int nameStartIndex = xClassName.LastIndexOf('.');
                if (nameStartIndex > 0)
                {
                    this.xClassNamespace = builderName.Substring(0, nameStartIndex);
                    xClassName = builderName.Substring(nameStartIndex + 1);
                }
 
                this.xClassNamespace = string.Format(CultureInfo.CurrentUICulture, "clr-namespace:{0}", this.xClassNamespace ?? string.Empty);
                this.xClassXamlType = new XamlType(this.xClassNamespace, xClassName, null, Writer.SchemaContext);
            }
 
            // Attributes [DependsOn("Name")]
            public void SetAttributes(XamlNodeQueue attributeNodes)
            {
                this.xClassAttributeNodes = attributeNodes;
            }
 
            // Properties [DependsOn("Attributes")]
            public void SetProperties(XamlNodeQueue propertyNodes, List<KeyValuePair<string, XamlNodeQueue>> defaultValueNodes)
            {
                this.xPropertiesNodes = propertyNodes;
                this.defaultValueNodes = defaultValueNodes;
 
                // exiting the properties tag. So we've now accrued any instances of Name and Attributes
                // that could possibly be hit flush our preamble
                FlushPreamble();
            }
 
            void FlushPreamble()
            {
                if (this.otherNodes == null) // already flushed
                {
                    return;
                }
                CurrentWriter = this.Writer.innerWriter;
                string classNamespace = null;
                // first, see if we need to emit a namespace corresponding to our class
                if (this.defaultValueNodes != null)
                {
                    classNamespace = this.xClassNamespace;
                }
 
                this.rootNode.FlushPendingNodes(classNamespace);
                this.rootNode = null; // not needed anymore
 
                CurrentWriter.WriteStartObject(this.Writer.activityXamlType);
 
                // first dump x:Class
                if (this.xClassNodes == null)
                {
                    SetXClass(null, null); // this will setup a default
                }
                XamlServices.Transform(this.xClassNodes.Reader, CurrentWriter, false);
 
                // String default values get written in attribute form immediately.
                // Other values get deferred until after x:Members, etc.
                XamlNodeQueue deferredPropertyNodes = null;
                if (this.defaultValueNodes != null)
                {
                    foreach (KeyValuePair<string, XamlNodeQueue> defaultValueNode in this.defaultValueNodes)
                    {
                        XamlReader reader = defaultValueNode.Value.Reader;
                        if (reader.Read())
                        {
                            bool isStringValue = false;
                            if (reader.NodeType == XamlNodeType.Value)
                            {
                                string stringValue = reader.Value as string;
                                if (stringValue != null)
                                {
                                    isStringValue = true;
                                }
                            }
                            if (isStringValue)
                            {
                                CurrentWriter.WriteStartMember(new XamlMember(defaultValueNode.Key, this.xClassXamlType, true));
                                CurrentWriter.WriteNode(reader);
                                XamlServices.Transform(defaultValueNode.Value.Reader, CurrentWriter, false);
                                // don't need an EndMember since it will be sitting in the node list (we only needed to strip the StartMember)                                
                            }
                            else
                            {
                                // Else: We'll write this out in a minute, after the x:ClassAttributes and x:Properties
                                if (deferredPropertyNodes == null)
                                {
                                    deferredPropertyNodes = new XamlNodeQueue(Writer.SchemaContext);
                                }
                                deferredPropertyNodes.Writer.WriteStartMember(new XamlMember(defaultValueNode.Key, this.xClassXamlType, true));
                                deferredPropertyNodes.Writer.WriteNode(reader);
                                XamlServices.Transform(defaultValueNode.Value.Reader, deferredPropertyNodes.Writer, false);
                            }
                        }
                    }
                }
 
                // then dump x:ClassAttributes if we have any
                if (this.xClassAttributeNodes != null)
                {
                    XamlServices.Transform(this.xClassAttributeNodes.Reader, CurrentWriter, false);
                }
 
                // and x:Properties
                if (this.xPropertiesNodes != null)
                {
                    XamlServices.Transform(this.xPropertiesNodes.Reader, CurrentWriter, false);
                }
 
                if (deferredPropertyNodes != null)
                {
                    XamlServices.Transform(deferredPropertyNodes.Reader, CurrentWriter, false);
                }
 
                if (this.otherNodes.Count > 0)
                {
                    XamlServices.Transform(this.otherNodes.Reader, CurrentWriter, false);
                }
                this.otherNodes = null; // done with this
            }
 
            protected internal override void Complete()
            {
                if (this.otherNodes != null)
                {
                    // need to flush
                    FlushPreamble();
                }
            }
 
            protected internal override void WriteStartMember(XamlMember xamlMember)
            {
                if (Writer.currentDepth == this.Depth + 1 && !xamlMember.IsAttachable)
                {
                    if (xamlMember == Writer.activityBuilderName)
                    {
                        // record that we're in ActivityBuilder.Name, since we'll need the class name for
                        // default value output
                        Writer.PushState(new BuilderNameNode(this, Writer));
                        return;
                    }
                    else if (xamlMember == Writer.activityBuilderAttributes)
                    {
                        // rewrite ActivityBuilder.Attributes to x:ClassAttributes
                        Writer.PushState(new AttributesNode(this, Writer));
                        return;
                    }
                    else if (xamlMember == Writer.activityBuilderProperties)
                    {
                        // rewrite ActivityBuilder.Properties to x:Members
                        Writer.PushState(new PropertiesNode(this, Writer));
                        return;
                    }
                    else
                    {
                        // any other member means we've passed properties due to [DependsOn] relationships
                        FlushPreamble();
                        if (xamlMember.DeclaringType == Writer.activityBuilderXamlType)
                        {
                            // Rewrite "<ActivityBuilder.XXX>" to "<Activity.XXX>"
                            xamlMember = Writer.activityXamlType.GetMember(xamlMember.Name);
                            if (xamlMember == null)
                            {
                                throw FxTrace.Exception.AsError(new InvalidOperationException(
                                    SR.MemberNotSupportedByActivityXamlServices(xamlMember.Name)));
                            }
 
                            if (xamlMember.Name == "Implementation")
                            {
                                Writer.PushState(new ImplementationNode(Writer));
                            }
                        }
                    }
                }
                base.WriteStartMember(xamlMember);
            }
        }
 
        // <ActivityBuilder.Name> node that we'll map to x:Class
        class BuilderNameNode : BuilderXamlNode
        {
            BuilderClassNode classNode;
            string builderName;
            XamlNodeQueue nameNodes;
 
            public BuilderNameNode(BuilderClassNode classNode, ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.classNode = classNode;
                this.nameNodes = new XamlNodeQueue(writer.SchemaContext);
                base.CurrentWriter = this.nameNodes.Writer;
            }
 
            protected internal override void Complete()
            {
                this.classNode.SetXClass(this.builderName, this.nameNodes);
            }
 
            protected internal override void WriteValue(object value)
            {
                if (Writer.currentDepth == this.Depth)
                {
                    this.builderName = (string)value;
                }
 
                base.WriteValue(value);
            }
        }
 
        // <ActivityBuilder.Attributes> node that we'll map to x:ClassAttributes
        class AttributesNode : BuilderXamlNode
        {
            XamlNodeQueue attributeNodes;
            BuilderClassNode classNode;
 
            public AttributesNode(BuilderClassNode classNode, ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.classNode = classNode;
                this.attributeNodes = new XamlNodeQueue(writer.SchemaContext);
                base.CurrentWriter = this.attributeNodes.Writer;
                CurrentWriter.WriteStartMember(XamlLanguage.ClassAttributes);
            }
 
            protected internal override void Complete()
            {
                this.classNode.SetAttributes(this.attributeNodes);
            }
        }
 
        // <ActivityBuilder.Properties> node that we'll map to x:Members
        // since x:Members doesn't have GetObject/StartMember wrappers around the value, we need to eat those
        class PropertiesNode : BuilderXamlNode
        {
            List<KeyValuePair<string, XamlNodeQueue>> defaultValueNodes;
            XamlNodeQueue propertiesNodes;
            BuilderClassNode classNode;
            bool skipGetObject;
 
            public PropertiesNode(BuilderClassNode classNode, ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.classNode = classNode;
                this.propertiesNodes = new XamlNodeQueue(writer.SchemaContext);
                base.CurrentWriter = this.propertiesNodes.Writer;
                CurrentWriter.WriteStartMember(XamlLanguage.Members);
            }
 
            protected internal override void WriteStartObject(XamlType xamlType)
            {
                if (xamlType == Writer.activityPropertyXamlType && Writer.currentDepth == this.Depth + 3)
                {
                    xamlType = XamlLanguage.Property;
                    Writer.PushState(new PropertyNode(this, Writer));
                }
                base.WriteStartObject(xamlType);
            }
 
            protected internal override void WriteGetObject()
            {
                if (Writer.currentDepth == this.Depth + 1)
                {
                    this.skipGetObject = true;
                }
                else
                {
                    base.WriteGetObject();
                }
            }
 
            protected internal override void WriteEndObject()
            {
                if (this.skipGetObject && Writer.currentDepth == this.Depth + 1)
                {
                    this.skipGetObject = false;
                }
                else
                {
                    base.WriteEndObject();
                }
            }
 
            protected internal override void WriteStartMember(XamlMember xamlMember)
            {
                if (this.skipGetObject && Writer.currentDepth == this.Depth + 2)
                {
                    return;
                }
                base.WriteStartMember(xamlMember);
            }
 
            protected internal override void WriteEndMember()
            {
                if (this.skipGetObject && Writer.currentDepth == this.Depth + 2)
                {
                    return;
                }
                base.WriteEndMember();
            }
 
            protected internal override void Complete()
            {
                this.classNode.SetProperties(this.propertiesNodes, this.defaultValueNodes);
            }
 
            public void AddDefaultValue(string propertyName, XamlNodeQueue value)
            {
                if (this.defaultValueNodes == null)
                {
                    this.defaultValueNodes = new List<KeyValuePair<string, XamlNodeQueue>>();
                }
 
                if (string.IsNullOrEmpty(propertyName))
                {
                    // default a name if one doesn't exist
                    propertyName = string.Format(CultureInfo.CurrentCulture, "_{0}", Guid.NewGuid().ToString().Replace("-", string.Empty));
                }
 
                this.defaultValueNodes.Add(new KeyValuePair<string, XamlNodeQueue>(propertyName, value));
            }
        }
 
        // <DynamicActivityProperty>...</DynamicActivityProperty>
        class PropertyNode : BuilderXamlNode
        {
            PropertiesNode properties;
            string propertyName;
            XamlType propertyType;
            XamlNodeQueue defaultValue;
 
            public PropertyNode(PropertiesNode properties, ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.properties = properties;
                base.CurrentWriter = properties.CurrentWriter;
            }
 
            public void SetName(string name)
            {
                this.propertyName = name;
            }
 
            public void SetType(XamlType type)
            {
                this.propertyType = type;
            }
 
            public void SetDefaultValue(XamlNodeQueue defaultValue)
            {
                this.defaultValue = defaultValue;
            }
 
            protected internal override void WriteStartMember(XamlMember xamlMember)
            {
                if (xamlMember.DeclaringType == Writer.activityPropertyXamlType && Writer.currentDepth == this.Depth + 1)
                {
                    if (xamlMember == Writer.activityPropertyName)
                    {
                        // record that we're in a property name, since we'll need this for default value output
                        Writer.PushState(new PropertyNameNode(this, Writer));
                        xamlMember = DynamicActivityXamlReader.xPropertyName;
                    }
                    else if (xamlMember == Writer.activityPropertyType)
                    {
                        // record that we're in a property type, since we'll need this for default value output
                        Writer.PushState(new PropertyTypeNode(this, Writer));
                        xamlMember = DynamicActivityXamlReader.xPropertyType;
                    }
                    else if (xamlMember == Writer.activityPropertyValue)
                    {
                        // record that we're in a property value, since we'll need this for default value output.
                        // don't write anything since we'll dump the default values after we exit ActivityBuilder.Properties
                        Writer.PushState(new PropertyValueNode(this, Writer));
                        xamlMember = null;
                    }
                }
 
                if (xamlMember != null)
                {
                    base.WriteStartMember(xamlMember);
                }
            }
 
            protected internal override void Complete()
            {
                if (this.defaultValue != null)
                {
                    if (string.IsNullOrEmpty(this.propertyName))
                    {
                        // default a name if one doesn't exist
                        this.propertyName = string.Format(CultureInfo.CurrentCulture, "_{0}", Guid.NewGuid().ToString().Replace("-", string.Empty));
                    }
 
                    if (this.defaultValue != null && this.propertyType != null)
                    {
                        // post-process the default value nodes to strip out 
                        // StartObject+StartMember _Initialization+EndMember+EndObject 
                        // wrapper nodes if the type of the object matches the 
                        // property Type (since we are moving from "object Value" to "T Value"
                        this.defaultValue = StripTypeWrapping(this.defaultValue, this.propertyType);
                    }
 
                    this.properties.AddDefaultValue(this.propertyName, this.defaultValue);
                }
            }
 
            static XamlNodeQueue StripTypeWrapping(XamlNodeQueue valueNodes, XamlType propertyType)
            {
                XamlNodeQueue targetNodes = new XamlNodeQueue(valueNodes.Reader.SchemaContext);
                XamlReader source = valueNodes.Reader;
                XamlWriter target = targetNodes.Writer;
                int depth = 0;
                bool consumeWrapperEndTags = false;
                bool hasBufferedStartObject = false;
 
                while (source.Read())
                {
                    switch (source.NodeType)
                    {
                        case XamlNodeType.StartObject:
                            depth++;
                            // only strip the wrapping type nodes if we have exactly this sequence:
                            // StartObject StartMember(Intialization) Value EndMember EndObject.
                            if (targetNodes.Count == 0 && depth == 1 && source.Type == propertyType && valueNodes.Count == 5)
                            {
                                hasBufferedStartObject = true;
                                continue;
                            }
                            break;
 
                        case XamlNodeType.GetObject:
                            depth++;
                            break;
 
                        case XamlNodeType.StartMember:
                            depth++;
                            if (hasBufferedStartObject)
                            {
                                if (depth == 2 && source.Member == XamlLanguage.Initialization)
                                {
                                    consumeWrapperEndTags = true;
                                    continue;
                                }
                                else
                                {
                                    hasBufferedStartObject = false;
                                    targetNodes.Writer.WriteStartObject(propertyType);
                                }
                            }
                            break;
 
                        case XamlNodeType.EndMember:
                            depth--;
                            if (consumeWrapperEndTags && depth == 1)
                            {
                                continue;
                            }
                            break;
 
                        case XamlNodeType.EndObject:
                            depth--;
                            if (consumeWrapperEndTags && depth == 0)
                            {
                                consumeWrapperEndTags = false;
                                continue;
                            }
                            break;
                    }
 
                    target.WriteNode(source);
                }
 
                return targetNodes;
            }
        }
 
        // <DynamicActivityProperty.Name>...</DynamicActivityProperty.Name>
        class PropertyNameNode : BuilderXamlNode
        {
            PropertyNode property;
 
            public PropertyNameNode(PropertyNode property, ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.property = property;
                base.CurrentWriter = property.CurrentWriter;
            }
 
            protected internal override void WriteValue(object value)
            {
                if (Writer.currentDepth == this.Depth)
                {
                    property.SetName((string)value);
                }
 
                base.WriteValue(value);
            }
        }
 
        // <DynamicActivityProperty.Type>...</DynamicActivityProperty.Type>
        class PropertyTypeNode : BuilderXamlNode
        {
            PropertyNode property;
 
            public PropertyTypeNode(PropertyNode property, ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.property = property;
                base.CurrentWriter = property.CurrentWriter;
            }
 
            protected internal override void WriteValue(object value)
            {
                if (Writer.currentDepth == this.Depth)
                {
                    // We only support property type as an attribute
                    XamlTypeName xamlTypeName = XamlTypeName.Parse(value as string, Writer.namespaceTable);
                    XamlType xamlType = Writer.SchemaContext.GetXamlType(xamlTypeName);
                    property.SetType(xamlType); // supports null
                }
 
                base.WriteValue(value);
            }
        }
 
        // <DynamicActivityProperty.Value>...</DynamicActivityProperty.Value>
        class PropertyValueNode : BuilderXamlNode
        {
            PropertyNode property;
            XamlNodeQueue valueNodes;
 
            public PropertyValueNode(PropertyNode property, ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.property = property;
                this.valueNodes = new XamlNodeQueue(writer.SchemaContext);
                base.CurrentWriter = this.valueNodes.Writer;
            }
 
            protected internal override void Complete()
            {
                this.property.SetDefaultValue(this.valueNodes);
                base.Complete();
            }
        }
 
        // <ActivityBuilder.Implementation>...</ActivityBuilder.Implementation>
        // We need to convert any <ActivityBuilder.PropertyReferences> inside here into <PropertyReferenceExtension>.       
        class ImplementationNode : BuilderXamlNode
        {
            Stack<ObjectFrame> objectStack;
 
            public ImplementationNode(ActivityBuilderXamlWriter writer)
                : base(writer)
            {
                this.objectStack = new Stack<ObjectFrame>();
            }
 
            internal void AddPropertyReference(ActivityPropertyReference propertyReference)
            {
                ObjectFrame currentFrame = this.objectStack.Peek();
                Fx.Assert(currentFrame.Type != null, "Should only create PropertyReferencesNode inside a StartObject");
                if (currentFrame.PropertyReferences == null)
                {
                    currentFrame.PropertyReferences = new List<ActivityPropertyReference>();
                }
                currentFrame.PropertyReferences.Add(propertyReference);
            }
 
            internal void SetUntransformedPropertyReferences(XamlMember propertyReferencesMember, XamlNodeQueue untransformedNodes)
            {
                ObjectFrame currentFrame = this.objectStack.Peek();
                Fx.Assert(currentFrame.Type != null, "Should only create PropertyReferencesNode inside a StartObject");
                currentFrame.AddMember(propertyReferencesMember, untransformedNodes);
            }
 
            protected internal override void WriteStartMember(XamlMember xamlMember)
            {
                ObjectFrame currentFrame = this.objectStack.Peek();
                if (currentFrame.Type == null)
                {
                    base.WriteStartMember(xamlMember);
                }
                else if (xamlMember == Writer.activityBuilderPropertyReference || xamlMember == Writer.activityBuilderPropertyReferences)
                {
                    // Parse out the contents of <ActivityBuilder.PropertyReferences> using a PropertyReferencesNode
                    Writer.PushState(new PropertyReferencesNode(Writer, xamlMember, this));
                }
                else
                {
                    this.CurrentWriter = currentFrame.StartMember(xamlMember, CurrentWriter);
                }
            }
 
            protected internal override void WriteStartObject(XamlType xamlType)
            {
                this.objectStack.Push(new ObjectFrame { Type = xamlType });
                base.WriteStartObject(xamlType);
            }
 
            protected internal override void WriteGetObject()
            {
                this.objectStack.Push(new ObjectFrame());
                base.WriteGetObject();
            }
 
            protected internal override void WriteEndObject()
            {
                ObjectFrame frame = this.objectStack.Pop();
                frame.FlushMembers(CurrentWriter);
                base.WriteEndObject();
            }
 
            protected internal override void WriteEndMember()
            {
                // Stack can be empty here if this is the EndMember that closes out the Node
                ObjectFrame currentFrame = this.objectStack.Count > 0 ? this.objectStack.Peek() : null;
                if (currentFrame == null || currentFrame.Type == null)
                {
                    base.WriteEndMember();
                }
                else
                {
                    CurrentWriter = currentFrame.EndMember();
                }
            }
 
            class ObjectFrame
            {
                XamlWriter parentWriter;
                XamlNodeQueue currentMemberNodes;
 
                public XamlType Type { get; set; }
                public XamlMember CurrentMember { get; set; }
                public List<KeyValuePair<XamlMember, XamlNodeQueue>> Members { get; set; }
                public List<ActivityPropertyReference> PropertyReferences { get; set; }
 
                public XamlWriter StartMember(XamlMember member, XamlWriter parentWriter)
                {
                    this.CurrentMember = member;
                    this.parentWriter = parentWriter;
                    this.currentMemberNodes = new XamlNodeQueue(parentWriter.SchemaContext);
                    return this.currentMemberNodes.Writer;
                }
 
                public XamlWriter EndMember()
                {
                    AddMember(this.CurrentMember, this.currentMemberNodes);
                    this.CurrentMember = null;
                    this.currentMemberNodes = null;
                    XamlWriter parentWriter = this.parentWriter;
                    this.parentWriter = null;
                    return parentWriter;
                }
 
                public void AddMember(XamlMember member, XamlNodeQueue content)
                {
                    if (this.Members == null)
                    {
                        this.Members = new List<KeyValuePair<XamlMember, XamlNodeQueue>>();
                    }
                    this.Members.Add(new KeyValuePair<XamlMember, XamlNodeQueue>(member, content));
                }
 
                public void FlushMembers(XamlWriter parentWriter)
                {
                    if (this.Type == null)
                    {
                        Fx.Assert(Members == null, "We shouldn't buffer members on GetObject");
                        return;
                    }
                    if (Members != null)
                    {
                        foreach (KeyValuePair<XamlMember, XamlNodeQueue> member in Members)
                        {
                            parentWriter.WriteStartMember(member.Key);
                            XamlServices.Transform(member.Value.Reader, parentWriter, false);
                            parentWriter.WriteEndMember();
                        }
                    }
                    if (PropertyReferences != null)
                    {
                        foreach (ActivityPropertyReference propertyReference in PropertyReferences)
                        {
                            XamlMember targetProperty = this.Type.GetMember(propertyReference.TargetProperty) ??
                                new XamlMember(propertyReference.TargetProperty, this.Type, false);
                            parentWriter.WriteStartMember(targetProperty);
                            WritePropertyReference(parentWriter, targetProperty, propertyReference.SourceProperty);
                            parentWriter.WriteEndMember();
                        }
                    }
                }
 
                void WritePropertyReference(XamlWriter parentWriter, XamlMember targetProperty, string sourceProperty)
                {
                    Type propertyReferenceType = typeof(PropertyReferenceExtension<>).MakeGenericType(targetProperty.Type.UnderlyingType ?? typeof(object));
                    XamlType propertyReferenceXamlType = parentWriter.SchemaContext.GetXamlType(propertyReferenceType);
                    parentWriter.WriteStartObject(propertyReferenceXamlType);
 
                    if (sourceProperty != null)
                    {
                        parentWriter.WriteStartMember(propertyReferenceXamlType.GetMember("PropertyName"));
                        parentWriter.WriteValue(sourceProperty);
                        parentWriter.WriteEndMember();
                    }
 
                    parentWriter.WriteEndObject();
                }
            }
        }
 
        // <ActivityBuilder.PropertyReference(s)> is stripped out and the inner
        // <ActivityPropertyReference>s map to PropertyReferenceNodes
        class PropertyReferencesNode : BuilderXamlNode
        {
            XamlNodeQueue untransformedNodes; // nodes that couldn't be transformed to PropertyReference form
            XamlMember originalStartMember;
 
            public PropertyReferencesNode(ActivityBuilderXamlWriter writer, XamlMember originalStartMember, ImplementationNode parent)
                : base(writer)
            {
                this.untransformedNodes = new XamlNodeQueue(Writer.SchemaContext);
                this.originalStartMember = originalStartMember;
                this.Parent = parent;
                base.CurrentWriter = this.untransformedNodes.Writer;
            }
 
            public bool HasUntransformedChildren { get; set; }
 
            public ImplementationNode Parent { get; private set; }
 
            public XamlWriter UntransformedNodesWriter { get { return this.untransformedNodes.Writer; } }
 
            protected internal override void WriteStartObject(XamlType xamlType)
            {
                if (xamlType == Writer.activityPropertyReferenceXamlType)
                {
                    Writer.PushState(new PropertyReferenceNode(this.Writer, this));
                    return;
                }
                base.WriteStartObject(xamlType);
            }
 
            protected internal override void WriteEndMember()
            {
                // We only want the untransformedNodes writer to contain our member contents, not the
                // Start/End members, so don't write our closing EM
                if (Writer.currentDepth != this.Depth)
                {
                    base.WriteEndMember();
                }
            }
 
            protected internal override void Complete()
            {
                if (this.HasUntransformedChildren)
                {
                    // Some ActivityPropertyReferences couldn't be transformed to properties. Leave them unchanged.
                    this.Parent.SetUntransformedPropertyReferences(this.originalStartMember, this.untransformedNodes);
                }
            }
        }
 
        // <ActivityPropertyReference TargetProperty="Foo" SourceProperty="RootActivityProperty"> maps to
        // <SomeClass.Foo><PropertyReference x:TypeArguments='targetType' PropertyName='RootActivityProperty'/></SomeClass.Foo>
        class PropertyReferenceNode : BuilderXamlNode
        {
            XamlNodeQueue propertyReferenceNodes;
            PropertyReferencesNode parent;
            string sourceProperty;
            string targetProperty;
            bool inSourceProperty;
            bool inTargetProperty;
 
            public PropertyReferenceNode(ActivityBuilderXamlWriter writer, PropertyReferencesNode parent)
                : base(writer)
            {
                this.propertyReferenceNodes = new XamlNodeQueue(writer.SchemaContext);
                this.parent = parent;
 
                // save the untransformed output in case we're not able to perform the transformation
                base.CurrentWriter = this.propertyReferenceNodes.Writer;
            }
 
            protected internal override void WriteStartMember(XamlMember xamlMember)
            {
                if (Writer.currentDepth == this.Depth + 1 // SM
                    && xamlMember.DeclaringType == Writer.activityPropertyReferenceXamlType)
                {
                    if (xamlMember.Name == "SourceProperty")
                    {
                        this.inSourceProperty = true;
                    }
                    else if (xamlMember.Name == "TargetProperty")
                    {
                        this.inTargetProperty = true;
                    }
                }
                base.WriteStartMember(xamlMember); // save output just in case
            }
 
            protected internal override void WriteValue(object value)
            {
                if (this.inSourceProperty)
                {
                    this.sourceProperty = (string)value;
                }
                else if (this.inTargetProperty)
                {
                    this.targetProperty = (string)value;
                }
                base.WriteValue(value); // save output just in case
            }
 
            protected internal override void WriteEndMember()
            {
                if (Writer.currentDepth == this.Depth + 1)
                {
                    this.inSourceProperty = false;
                    this.inTargetProperty = false;
                }
                base.WriteEndMember(); // save output just in case
            }
 
            protected internal override void Complete()
            {
                if (this.targetProperty == null)
                {
                    // can't transform to <Foo.></Foo.>, dump original nodes <ActivityBuilder.PropertyReference(s) .../>
                    this.parent.HasUntransformedChildren = true;
                    this.parent.UntransformedNodesWriter.WriteStartObject(Writer.activityPropertyReferenceXamlType);
                    XamlServices.Transform(this.propertyReferenceNodes.Reader, this.parent.UntransformedNodesWriter, false);
                }
                else
                {
                    ActivityPropertyReference propertyReference = new ActivityPropertyReference
                    {
                        SourceProperty = this.sourceProperty,
                        TargetProperty = this.targetProperty
                    };
                    parent.Parent.AddPropertyReference(propertyReference);
                }
            }
        }
    }
}