File: System\Data\Mapping\MetadataMappingHasherVisitor.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="MetadataMappingHasherVisitor.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
 
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Metadata.Edm;
using System.Data.Common;
using System.Data.Common.Utils;
using System.Data.Mapping;
using System.Diagnostics;
using System.Globalization;
 
 
namespace System.Data.Mapping
{
    internal partial class MetadataMappingHasherVisitor : BaseMetadataMappingVisitor
    {
        private CompressingHashBuilder m_hashSourceBuilder;
        private Dictionary<Object, int> m_itemsAlreadySeen = new Dictionary<Object, int>();
        private int m_instanceNumber = 0;
        private EdmItemCollection m_EdmItemCollection;
        private double m_EdmVersion;
        private double m_MappingVersion;
 
        private MetadataMappingHasherVisitor(double mappingVersion)
        {
            m_MappingVersion = mappingVersion;
            this.m_hashSourceBuilder = new CompressingHashBuilder(MetadataHelper.CreateMetadataHashAlgorithm(m_MappingVersion));
        }
        
        #region visitor method
        protected override void Visit(StorageEntityContainerMapping storageEntityContainerMapping)
        {
            Debug.Assert(storageEntityContainerMapping != null, "storageEntityContainerMapping cannot be null!");
 
            // at the entry point of visitor, we setup the versions
            Debug.Assert(m_MappingVersion == storageEntityContainerMapping.StorageMappingItemCollection.MappingVersion, "the original version and the mapping collection version are not the same");
            this.m_MappingVersion = storageEntityContainerMapping.StorageMappingItemCollection.MappingVersion;
            this.m_EdmVersion = storageEntityContainerMapping.StorageMappingItemCollection.EdmItemCollection.EdmVersion;
 
            this.m_EdmItemCollection = storageEntityContainerMapping.StorageMappingItemCollection.EdmItemCollection;
 
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(storageEntityContainerMapping, out index))
            {
                // if this has been add to the seen list, then just 
                return;
            }
            if (this.m_itemsAlreadySeen.Count > 1)
            {
 
                // this means user try another visit over SECM, this is allowed but all the previous visit all lost due to clean
                // user can visit different SECM objects by using the same visitor to load the SECM object
                this.Clean();
                Visit(storageEntityContainerMapping);
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(storageEntityContainerMapping, index);
 
            #region Inner data visit
 
            this.AddObjectContentToHashBuilder(storageEntityContainerMapping.Identity);
 
            this.AddV2ObjectContentToHashBuilder(storageEntityContainerMapping.GenerateUpdateViews, this.m_MappingVersion);
 
            base.Visit(storageEntityContainerMapping);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(EntityContainer entityContainer)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(entityContainer, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(entityContainer, index);
 
            #region Inner data visit
            
            this.AddObjectContentToHashBuilder(entityContainer.Identity);
            // Name is covered by Identity
 
            base.Visit(entityContainer);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(StorageSetMapping storageSetMapping)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(storageSetMapping, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(storageSetMapping, index);
 
            #region Inner data visit
            base.Visit(storageSetMapping);
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(StorageTypeMapping storageTypeMapping)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(storageTypeMapping, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(storageTypeMapping, index);
 
            #region Inner data visit
 
            base.Visit(storageTypeMapping);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(StorageMappingFragment storageMappingFragment)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(storageMappingFragment, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(storageMappingFragment, index);
 
            #region Inner data visit
 
            this.AddV2ObjectContentToHashBuilder(storageMappingFragment.IsSQueryDistinct, this.m_MappingVersion);
 
            base.Visit(storageMappingFragment);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
        
        protected override void Visit(StoragePropertyMapping storagePropertyMapping)
        {
            base.Visit(storagePropertyMapping);
        }
 
        protected override void Visit(StorageComplexPropertyMapping storageComplexPropertyMapping)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(storageComplexPropertyMapping, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(storageComplexPropertyMapping, index);
 
            #region Inner data visit
 
            base.Visit(storageComplexPropertyMapping);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
        protected override void Visit(StorageComplexTypeMapping storageComplexTypeMapping)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(storageComplexTypeMapping, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(storageComplexTypeMapping, index);
 
            #region Inner data visit
 
            base.Visit(storageComplexTypeMapping);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
        
        protected override void Visit(StorageConditionPropertyMapping storageConditionPropertyMapping)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(storageConditionPropertyMapping, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(storageConditionPropertyMapping, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(storageConditionPropertyMapping.IsNull);
            this.AddObjectContentToHashBuilder(storageConditionPropertyMapping.Value);
 
            base.Visit(storageConditionPropertyMapping);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(StorageScalarPropertyMapping storageScalarPropertyMapping)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(storageScalarPropertyMapping, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(storageScalarPropertyMapping, index);
 
            #region Inner data visit
 
            base.Visit(storageScalarPropertyMapping);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
        
        protected override void Visit(EntitySetBase entitySetBase)
        {
            base.Visit(entitySetBase);
        }
        
        protected override void Visit(EntitySet entitySet)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(entitySet, out index))
            {
                return;
            }
            #region Inner data visit
 
            this.AddObjectStartDumpToHashBuilder(entitySet, index);
            this.AddObjectContentToHashBuilder(entitySet.Name);
            this.AddObjectContentToHashBuilder(entitySet.Schema);
            this.AddObjectContentToHashBuilder(entitySet.Table);
 
            base.Visit(entitySet);
 
            foreach (var entityType in MetadataHelper.GetTypeAndSubtypesOf(entitySet.ElementType, this.m_EdmItemCollection, false).Where(type => type != entitySet.ElementType))
            {
                this.Visit(entityType);
            }
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(AssociationSet associationSet)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(associationSet, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(associationSet, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(associationSet.CachedProviderSql);
            // Name is coverd by Identity
            this.AddObjectContentToHashBuilder(associationSet.Identity);
            this.AddObjectContentToHashBuilder(associationSet.Schema);
            this.AddObjectContentToHashBuilder(associationSet.Table);
 
            base.Visit(associationSet);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(EntityType entityType)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(entityType, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(entityType, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(entityType.Abstract);
            this.AddObjectContentToHashBuilder(entityType.Identity);
            // FullName, Namespace and Name are all covered by Identity
 
            base.Visit(entityType);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(AssociationSetEnd associationSetEnd)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(associationSetEnd, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(associationSetEnd, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(associationSetEnd.Identity);
            // Name is covered by Identity
 
            base.Visit(associationSetEnd);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(AssociationType associationType)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(associationType, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(associationType, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(associationType.Abstract);
            this.AddObjectContentToHashBuilder(associationType.Identity);
            // FullName, Namespace, and Name are all covered by Identity
 
            base.Visit(associationType);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(EdmProperty edmProperty)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(edmProperty, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(edmProperty, index);
 
            #region Inner data visit
            // since the delaring type is fixed and referenced to the upper type, 
            // there is no need to hash this
            //this.AddObjectContentToHashBuilder(edmProperty.DeclaringType);
            this.AddObjectContentToHashBuilder(edmProperty.DefaultValue);
            this.AddObjectContentToHashBuilder(edmProperty.Identity);
            // Name is covered by Identity
            this.AddObjectContentToHashBuilder(edmProperty.IsStoreGeneratedComputed);
            this.AddObjectContentToHashBuilder(edmProperty.IsStoreGeneratedIdentity);
            this.AddObjectContentToHashBuilder(edmProperty.Nullable);
 
            base.Visit(edmProperty);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(NavigationProperty navigationProperty)
        {
            // navigation properties are not considered in view generation
            return;
        }
 
        protected override void Visit(EdmMember edmMember)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(edmMember, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(edmMember, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(edmMember.Identity);
            // Name is covered by Identity
            this.AddObjectContentToHashBuilder(edmMember.IsStoreGeneratedComputed);
            this.AddObjectContentToHashBuilder(edmMember.IsStoreGeneratedIdentity);
 
            base.Visit(edmMember);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(AssociationEndMember associationEndMember)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(associationEndMember, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(associationEndMember, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(associationEndMember.DeleteBehavior);
            this.AddObjectContentToHashBuilder(associationEndMember.Identity);
            // Name is covered by Identity
            this.AddObjectContentToHashBuilder(associationEndMember.IsStoreGeneratedComputed);
            this.AddObjectContentToHashBuilder(associationEndMember.IsStoreGeneratedIdentity);
            this.AddObjectContentToHashBuilder(associationEndMember.RelationshipMultiplicity);
 
            base.Visit(associationEndMember);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
        
        protected override void Visit(ReferentialConstraint referentialConstraint)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(referentialConstraint, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(referentialConstraint, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(referentialConstraint.Identity);
 
            base.Visit(referentialConstraint);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(RelationshipEndMember relationshipEndMember)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(relationshipEndMember, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(relationshipEndMember, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(relationshipEndMember.DeleteBehavior);
            this.AddObjectContentToHashBuilder(relationshipEndMember.Identity);
            // Name is covered by Identity
            this.AddObjectContentToHashBuilder(relationshipEndMember.IsStoreGeneratedComputed);
            this.AddObjectContentToHashBuilder(relationshipEndMember.IsStoreGeneratedIdentity);
            this.AddObjectContentToHashBuilder(relationshipEndMember.RelationshipMultiplicity);
 
            base.Visit(relationshipEndMember);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(TypeUsage typeUsage)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(typeUsage, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(typeUsage, index);
 
            #region Inner data visit
            //No need to add identity of TypeUsage to the hash since it would take into account
            //facets that viewgen would not care and we visit the important facets anyway.
 
            base.Visit(typeUsage);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(RelationshipType relationshipType)
        {
            base.Visit(relationshipType);
        }
 
        protected override void Visit(EdmType edmType)
        {
            base.Visit(edmType);
        }
        
        protected override void Visit(EnumType enumType)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(enumType, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(enumType, index);
 
            this.AddObjectContentToHashBuilder(enumType.Identity);
            this.Visit(enumType.UnderlyingType);
 
            base.Visit(enumType);
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(EnumMember enumMember)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(enumMember, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(enumMember, index);
 
            this.AddObjectContentToHashBuilder(enumMember.Name);
            this.AddObjectContentToHashBuilder(enumMember.Value);
 
            base.Visit(enumMember);
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(CollectionType collectionType)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(collectionType, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(collectionType, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(collectionType.Identity);
            // Identity contains Name, NamespaceName and FullName
 
            base.Visit(collectionType);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
        
        protected override void Visit(RefType refType)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(refType, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(refType, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(refType.Identity);
            // Identity contains Name, NamespaceName and FullName
 
            base.Visit(refType);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(EntityTypeBase entityTypeBase)
        {
            base.Visit(entityTypeBase);
        }
 
        protected override void Visit(Facet facet)
        {
            int index;
            if (facet.Name != DbProviderManifest.NullableFacetName)
            {
                // skip all the non interesting facets
                return;
            }
 
            if (!this.AddObjectToSeenListAndHashBuilder(facet, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(facet, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(facet.Identity);
            // Identity already contains Name
            this.AddObjectContentToHashBuilder(facet.Value);
 
            base.Visit(facet);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(EdmFunction edmFunction)
        {
            // View Generation doesn't deal with functions
            // so just return;
        }
        
        protected override void Visit(ComplexType complexType)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(complexType, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(complexType, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(complexType.Abstract);
            this.AddObjectContentToHashBuilder(complexType.Identity);
            // Identity covers, FullName, Name, and NamespaceName
 
            base.Visit(complexType);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
        
        protected override void Visit(PrimitiveType primitiveType)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(primitiveType, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(primitiveType, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(primitiveType.Name);
            this.AddObjectContentToHashBuilder(primitiveType.NamespaceName);
 
            base.Visit(primitiveType);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
        
        protected override void Visit(FunctionParameter functionParameter)
        {
            int index;
            if (!this.AddObjectToSeenListAndHashBuilder(functionParameter, out index))
            {
                return;
            }
 
            this.AddObjectStartDumpToHashBuilder(functionParameter, index);
 
            #region Inner data visit
            this.AddObjectContentToHashBuilder(functionParameter.Identity);
            // Identity already has Name
            this.AddObjectContentToHashBuilder(functionParameter.Mode);
 
            base.Visit(functionParameter);
 
            #endregion
 
            this.AddObjectEndDumpToHashBuilder();
        }
 
        protected override void Visit(DbProviderManifest providerManifest)
        {
            // the provider manifest will be checked by all the other types lining up.
            // no need to store more info.
        }
        #endregion
 
        #region hasher helper method
 
        internal string HashValue
        {
            get
            {
                return m_hashSourceBuilder.ComputeHash();
            }
        }
 
        private void Clean()
        {
            this.m_hashSourceBuilder = new CompressingHashBuilder(MetadataHelper.CreateMetadataHashAlgorithm(m_MappingVersion));
            this.m_instanceNumber = 0;
            this.m_itemsAlreadySeen = new Dictionary<object, int>();
        }
 
        /// <summary>
        /// if already seen, then out the object instance index, return false;
        /// if haven't seen, then add it to the m_itemAlreadySeen, out the current index, return true
        /// </summary>
        /// <param name="o"></param>
        /// <param name="indexSeen"></param>
        /// <returns></returns>
        private bool TryAddSeenItem(Object o, out int indexSeen)
        {
            if (!this.m_itemsAlreadySeen.TryGetValue(o, out indexSeen))
            {
                this.m_itemsAlreadySeen.Add(o, this.m_instanceNumber);
 
                indexSeen = this.m_instanceNumber;
                this.m_instanceNumber++;
 
                return true;
            }
            return false;
        }
 
        /// <summary>
        /// if the object has seen, then add the seen object style to the hash source, return false;
        /// if not, then add it to the seen list, and append the object start dump to the hash source, return true
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        private bool AddObjectToSeenListAndHashBuilder(object o, out int instanceIndex)
        {
            if (o == null)
            {
                instanceIndex = -1;
                return false;
            }
            if (!TryAddSeenItem(o, out instanceIndex))
            {
                this.AddObjectStartDumpToHashBuilder(o, instanceIndex);
                this.AddSeenObjectToHashBuilder(o, instanceIndex);
                this.AddObjectEndDumpToHashBuilder();
                return false;
            }
            return true;
        }
 
        private void AddSeenObjectToHashBuilder(object o, int instanceIndex)
        {
            Debug.Assert(instanceIndex >= 0, "referencing index should not be less than 0");
            this.m_hashSourceBuilder.AppendLine("Instance Reference: " + instanceIndex);
        }
 
        private void AddObjectStartDumpToHashBuilder(object o, int objectIndex)
        {
            this.m_hashSourceBuilder.AppendObjectStartDump(o, objectIndex);
        }
 
        private void AddObjectEndDumpToHashBuilder()
        {
            this.m_hashSourceBuilder.AppendObjectEndDump();
        }
 
        private void AddObjectContentToHashBuilder(object content)
        {
            if (content != null)
            {
                IFormattable formatContent = content as IFormattable;
                if (formatContent != null)
                {
                    // if the content is formattable, the following code made it culture invariant,
                    // for instance, the int, "30,000" can be formatted to "30-000" if the user 
                    // has a different language and region setting
                    this.m_hashSourceBuilder.AppendLine(formatContent.ToString(null, CultureInfo.InvariantCulture));
                }
                else
                {
                    this.m_hashSourceBuilder.AppendLine(content.ToString());
                }
            }
            else
            {
                this.m_hashSourceBuilder.AppendLine("NULL");
            }
        }
 
        /// <summary>
        /// Add V2 schema properties and attributes to the hash builder
        /// </summary>
        /// <param name="content"></param>
        /// <param name="defaultValue"></param>
        private void AddV2ObjectContentToHashBuilder(object content, double version)
        {
            // if the version number is greater than or equal to V2, then we add the value
            if (version >= XmlConstants.EdmVersionForV2)
            {
                this.AddObjectContentToHashBuilder(content);
            }
        }
 
        internal static string GetMappingClosureHash(double mappingVersion, StorageEntityContainerMapping storageEntityContainerMapping)
        {
            Debug.Assert(storageEntityContainerMapping != null, "storageEntityContainerMapping is null!");
 
            MetadataMappingHasherVisitor visitor = new MetadataMappingHasherVisitor(mappingVersion);
            visitor.Visit(storageEntityContainerMapping);
            return visitor.HashValue;
        }
        #endregion
 
    }
}