private void SerializeEntityRelationships(XmlWriter xmlWriter, EntityHierarchyEntry entityNode, Stack <string> xmlStack)
        {
            if (entityNode.ForwardRelationships != null)
            {
                string relTag = XmlConstants.RelationshipConstants.Rel;

                foreach (RelationshipEntry relationship in entityNode.ForwardRelationships)
                {
                    using (WriteElementBlock(xmlWriter, relationship.TypeId, relTag, relTag, XmlConstants.Id, xmlStack, false))
                    {
                        xmlWriter.WriteString(GetInnerText(relationship.ToId));
                    }
                }
            }

            if (entityNode.ReverseRelationships != null)
            {
                foreach (RelationshipEntry relationship in entityNode.ReverseRelationships)
                {
                    string relTag = XmlConstants.RelationshipConstants.RevRel;

                    using (WriteElementBlock(xmlWriter, relationship.TypeId, relTag, relTag, XmlConstants.Id, xmlStack, true))
                    {
                        xmlWriter.WriteString(GetInnerText(relationship.FromId));
                    }
                }
            }
        }
        private void SerializeNestedEntities(XmlWriter xmlWriter, EntityHierarchyEntry entityNode, Stack <string> xmlStack)
        {
            if (entityNode.Children == null)
            {
                return;
            }

            // Children grouped by rel type
            var childrenByType = entityNode.Children
                                 .GroupBy(e => new Tuple <Direction, Guid>(e.Direction, e.RelationshipFromParent.TypeId))
                                 .OrderBy(t => t.Key);

            foreach (var childList in childrenByType)
            {
                Direction dir       = childList.Key.Item1;
                Guid      relTypeId = childList.Key.Item2;

                string relTag = dir == Direction.Forward ? XmlConstants.RelationshipConstants.Rel : XmlConstants.RelationshipConstants.RevRel;

                using (WriteElementBlock(xmlWriter, relTypeId, relTag, relTag, XmlConstants.Id, xmlStack, dir == Direction.Reverse))
                {
                    foreach (EntityHierarchyEntry childNode in childList)
                    {
                        SerializeEntity(xmlWriter, childNode, xmlStack);
                    }
                }
            }
        }
        /// <summary>
        ///     Handle case if one entity is a component of the other.
        /// </summary>
        /// <param name="relType">The relationship type.</param>
        /// <param name="fromEntity">The 'from' entity.</param>
        /// <param name="toEntity">The 'to' entity.</param>
        /// <returns></returns>
        private bool HandleContainedEntities(RelationshipEntry rel, RelationshipTypeEntry relType, EntityHierarchyEntry fromEntity, EntityHierarchyEntry toEntity)
        {
            if (relType == null)
            {
                return(false);
            }

            // Explicitly do not next certain relationships
            if (rel.TypeId == Guids.InSolution || rel.TypeId == Guids.IndirectInSolution)
            {
                return(false);
            }

            // if 'fwdComponent' then parent = FromId
            // if 'revComponent' then parent = ToId
            // "Clone action" is the best proxy to determine if something is a subcomponent, while still handling 'custom' reltypes.
            bool fwdComponent = relType.CloneAction == CloneActionEnum_Enumeration.CloneEntities;
            bool revComponent = !fwdComponent && relType.ReverseCloneAction == CloneActionEnum_Enumeration.CloneEntities;

            EntityHierarchyEntry parent = null;
            EntityHierarchyEntry child  = null;

            if (fwdComponent)
            {
                parent = fromEntity;
                child  = toEntity;
            }
            else if (revComponent)
            {
                parent = toEntity;
                child  = fromEntity;
            }
            else
            {
                return(false);
            }

            if (child.ParentEntity != null)
            {
                return(false);   // child already assigned to a different parent
            }
            if (WouldCauseCycle(parent, child))
            {
                return(false);
            }

            if (parent.Children == null)
            {
                parent.Children = new List <EntityHierarchyEntry>( );
            }
            parent.Children.Add(child);
            child.ParentEntity           = parent;
            child.RelationshipFromParent = rel;
            child.Direction = revComponent ? Direction.Reverse : Direction.Forward;
            return(true);
        }
        /// <summary>
        ///     Returns true if adding this child to this parent would cause a cycle.
        /// </summary>
        /// <param name="parent">The potential parent</param>
        /// <param name="child">The potential child</param>
        /// <returns>True if a cycle would result.</returns>
        private bool WouldCauseCycle(EntityHierarchyEntry parent, EntityHierarchyEntry child)
        {
            EntityHierarchyEntry current = parent;

            while (current != null)
            {
                if (current == child)
                {
                    return(true);
                }
                current = current.ParentEntity;
            }
            return(false);
        }
        private void SerializeEntity(XmlWriter xmlWriter, EntityHierarchyEntry entityNode, Stack <string> xmlStack)
        {
            EntityEntry entity = entityNode.Entity;

            Guid typeId = entityNode.TypeRelationship?.ToId ?? Guid.Empty;

            WriteEntityNameComment(xmlWriter, entity);

            // TODO : HANDLE MISSING TYPE RELATIONSHIP PROPERLY

            using (WriteElementBlock(xmlWriter, typeId, XmlConstants.EntityConstants.Type, XmlConstants.EntityConstants.Entity, XmlConstants.EntityConstants.TypeId, xmlStack))
            {
                xmlWriter.WriteAttributeString(XmlConstants.Id, XmlConvert.ToString(entity.EntityId));

                SerializeEntityFields(xmlWriter, entity, xmlStack);

                SerializeEntityRelationships(xmlWriter, entityNode, xmlStack);

                SerializeNestedEntities(xmlWriter, entityNode, xmlStack);
            }
        }
        /// <summary>
        ///     Serializes the entities.
        /// </summary>
        /// <param name="xmlWriter">The XML writer.</param>
        /// <param name="xmlStack">The XML stack.</param>
        private void SerializeEntities(XmlWriter xmlWriter, Stack <string> xmlStack)
        {
            IEnumerable <EntityEntry> entities = PackageData.Entities;

            HierarchyBuilder builder = new HierarchyBuilder( );

            builder.RelationshipMetadataCallback = PackageData.Metadata.RelationshipTypeCallback;

            _root = builder.BuildEntityHierarchy(PackageData);

            /////
            // By this point the entire entity graph has been built. Now serialize it
            /////
            using (xmlWriter.WriteElementBlock(XmlConstants.EntityConstants.Entities, xmlStack))
            {
                if (_root.Children != null)
                {
                    var typeGroups = _root.Children
                                     .GroupBy(e => e.TypeRelationship?.ToId)
                                     .Select(g => new { TypeId = GuidOrAlias(g?.Key), Group = g })
                                     .OrderBy(g => TypeOrder(g.TypeId)) // move 'solution' to the top
                                     .ThenBy(g => g.TypeId);

                    foreach (var group in typeGroups)
                    {
                        using (xmlWriter.WriteElementBlock(XmlConstants.EntityConstants.Group, xmlStack))
                        {
                            string typeId = group.TypeId;
                            if (typeId != null)
                            {
                                // Note : this is purely for grouping cosmetics, do not use it for reading information.
                                xmlWriter.WriteAttributeString(XmlConstants.EntityConstants.TypeId, typeId);
                            }

                            SerializeEntityList(xmlWriter, group.Group, xmlStack);
                        }
                    }
                }
            }
        }
        public EntityHierarchyEntry BuildEntityHierarchy([NotNull] PackageData packageData)
        {
            if (packageData == null)
            {
                throw new ArgumentNullException(nameof(packageData));
            }

            _root = new EntityHierarchyEntry
            {
                Children             = new List <EntityHierarchyEntry>( ),
                ForwardRelationships = new List <RelationshipEntry>( )
            };

            // Create dictionary of entity nodes
            if (packageData.Entities != null)
            {
                _entities = packageData.Entities.ToDictionary(
                    e => e.EntityId, e => new EntityHierarchyEntry {
                    Entity = e
                });
            }
            else
            {
                _entities = new Dictionary <Guid, EntityHierarchyEntry>( );
            }


            // Walk all relationships
            // Note: relationships get canonically ordered to ensure deterministic construction of hierarchy
            foreach (RelationshipEntry rel in packageData.Relationships.OrderBy(r => r.TypeId).ThenBy(r => r.FromId).ThenBy(r => r.ToId))
            {
                EntityHierarchyEntry fromEntity;
                EntityHierarchyEntry toEntity;
                _entities.TryGetValue(rel.FromId, out fromEntity);
                _entities.TryGetValue(rel.ToId, out toEntity);

                // Handle 'type' relationship
                if (fromEntity != null && rel.TypeId == Guids.IsOfType && fromEntity.TypeRelationship == null)
                {
                    fromEntity.TypeRelationship = rel;
                    continue;
                }

                RelationshipTypeEntry relType = GetRelationshipMetadata(rel.TypeId);

                // Detect entities that contain entities
                if (fromEntity != null && toEntity != null)
                {
                    if (HandleContainedEntities(rel, relType, fromEntity, toEntity))
                    {
                        continue;
                    }
                }

                // Other relationships - find somewhere to add them.
                if (fromEntity != null)
                {
                    if (fromEntity.ForwardRelationships == null)
                    {
                        fromEntity.ForwardRelationships = new List <RelationshipEntry>( );
                    }
                    fromEntity.ForwardRelationships.Add(rel);
                    continue;
                }

                if (toEntity != null)
                {
                    if (toEntity.ReverseRelationships == null)
                    {
                        toEntity.ReverseRelationships = new List <RelationshipEntry>( );
                    }
                    toEntity.ReverseRelationships.Add(rel);
                    continue;
                }

                // Nowhere else to add them..
                _root.ForwardRelationships.Add(rel);
            }

            // Gather top tier entities into root entity
            foreach (var entity in _entities.Values)
            {
                if (entity.ParentEntity != null)
                {
                    continue;
                }
                _root.Children.Add(entity);
                entity.ParentEntity = _root;
            }

            return(_root);
        }