text: //---------------------------------------------------------------------
  // <copyright file="TypeResolver.cs" company="Microsoft">
  //      Copyright (c) Microsoft Corporation.  All rights reserved.
  // </copyright>
  //
  // @owner  Microsoft
  // @backupOwner Microsoft
  //---------------------------------------------------------------------
  
  namespace System.Data.Common.EntitySql
  {
      using System;
      using System.Collections.Generic;
      using System.Data.Entity;
      using System.Data.Metadata.Edm;
      using System.Diagnostics;
      using System.Linq;
  
      /// <summary>
      /// Represents eSQL metadata member expression class.
      /// </summary>
      internal enum MetadataMemberClass
      {
          Type,
          FunctionGroup,
          InlineFunctionGroup,
          Namespace,
          EnumMember
      }
  
      /// <summary>
      /// Abstract class representing an eSQL expression classified as <see cref="ExpressionResolutionClass.MetadataMember"/>.
      /// </summary>
      internal abstract class MetadataMember : ExpressionResolution
      {
          protected MetadataMember(MetadataMemberClass @class, string name)
              : base(ExpressionResolutionClass.MetadataMember)
          {
              Debug.Assert(!String.IsNullOrEmpty(name), "name must not be empty");
  
              MetadataMemberClass = @class;
              Name = name;
          }
  
          internal override string ExpressionClassName { get { return MetadataMemberExpressionClassName; } }
          internal static string MetadataMemberExpressionClassName { get { return Strings.LocalizedMetadataMemberExpression; } }
  
          internal readonly MetadataMemberClass MetadataMemberClass;
          internal readonly string Name;
          /// <summary>
          /// Return the name of the <see cref="MetadataMemberClass"/> for error messages.
          /// </summary>
          internal abstract string MetadataMemberClassName { get; }
  
          internal static IEqualityComparer<MetadataMember> CreateMetadataMemberNameEqualityComparer(StringComparer stringComparer)
          {
              return new MetadataMemberNameEqualityComparer(stringComparer);
          }
  
          private sealed class MetadataMemberNameEqualityComparer : IEqualityComparer<MetadataMember>
          {
              private readonly StringComparer _stringComparer;
  
              internal MetadataMemberNameEqualityComparer(StringComparer stringComparer)
              {
                  _stringComparer = stringComparer;
              }
  
              bool IEqualityComparer<MetadataMember>.Equals(MetadataMember x, MetadataMember y)
              {
                  Debug.Assert(x != null && y != null, "metadata members must not be null");
                  return _stringComparer.Equals(x.Name, y.Name);
              }
  
              int IEqualityComparer<MetadataMember>.GetHashCode(MetadataMember obj)
              {
                  Debug.Assert(obj != null, "metadata member must not be null");
                  return _stringComparer.GetHashCode(obj.Name);
              }
          }
      }
  
      /// <summary>
      /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.Namespace"/>.
      /// </summary>
      internal sealed class MetadataNamespace : MetadataMember
      {
          internal MetadataNamespace(string name) : base(MetadataMemberClass.Namespace, name) { }
  
          internal override string MetadataMemberClassName { get { return NamespaceClassName; } }
          internal static string NamespaceClassName { get { return Strings.LocalizedNamespace; } }
      }
  
      /// <summary>
      /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.Type"/>.
      /// </summary>
      internal sealed class MetadataType : MetadataMember
      {
          internal MetadataType(string name, TypeUsage typeUsage)
              : base(MetadataMemberClass.Type, name)
          {
              Debug.Assert(typeUsage != null, "typeUsage must not be null");
              TypeUsage = typeUsage;
          }
  
          internal override string MetadataMemberClassName { get { return TypeClassName; } }
          internal static string TypeClassName { get { return Strings.LocalizedType; } }
  
          internal readonly TypeUsage TypeUsage;
      }
  
      /// <summary>
      /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.EnumMember"/>.
      /// </summary>
      internal sealed class MetadataEnumMember : MetadataMember
      {
          internal MetadataEnumMember(string name, TypeUsage enumType, EnumMember enumMember)
              : base(MetadataMemberClass.EnumMember, name)
          {
              Debug.Assert(enumType != null, "enumType must not be null");
              Debug.Assert(enumMember != null, "enumMember must not be null");
              EnumType = enumType;
              EnumMember = enumMember;
          }
  
          internal override string MetadataMemberClassName { get { return EnumMemberClassName; } }
          internal static string EnumMemberClassName { get { return Strings.LocalizedEnumMember; } }
  
          internal readonly TypeUsage EnumType;
          internal readonly EnumMember EnumMember;
      }
  
      /// <summary>
      /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.FunctionGroup"/>.
      /// </summary>
      internal sealed class MetadataFunctionGroup : MetadataMember
      {
          internal MetadataFunctionGroup(string name, IList<EdmFunction> functionMetadata)
              : base(MetadataMemberClass.FunctionGroup, name)
          {
              Debug.Assert(functionMetadata != null && functionMetadata.Count > 0, "FunctionMetadata must not be null or empty");
              FunctionMetadata = functionMetadata;
          }
  
          internal override string MetadataMemberClassName { get { return FunctionGroupClassName; } }
          internal static string FunctionGroupClassName { get { return Strings.LocalizedFunction; } }
  
          internal readonly IList<EdmFunction> FunctionMetadata;
      }
  
      /// <summary>
      /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.InlineFunctionGroup"/>.
      /// </summary>
      internal sealed class InlineFunctionGroup : MetadataMember
      {
          internal InlineFunctionGroup(string name, IList<InlineFunctionInfo> functionMetadata)
              : base(MetadataMemberClass.InlineFunctionGroup, name)
          {
              Debug.Assert(functionMetadata != null && functionMetadata.Count > 0, "FunctionMetadata must not be null or empty");
              FunctionMetadata = functionMetadata;
          }
  
          internal override string MetadataMemberClassName { get { return InlineFunctionGroupClassName; } }
          internal static string InlineFunctionGroupClassName { get { return Strings.LocalizedInlineFunction; } }
  
          internal readonly IList<InlineFunctionInfo> FunctionMetadata;
      }
  
      /// <summary>
      /// Represents eSQL type and namespace name resolver.
      /// </summary>
      internal sealed class TypeResolver
      {
          private readonly Perspective _perspective;
          private readonly ParserOptions _parserOptions;
          private readonly Dictionary<string, MetadataNamespace> _aliasedNamespaces;
          private readonly HashSet<MetadataNamespace> _namespaces;
          /// <summary>
          /// name -> list(overload)
          /// </summary>
          private readonly Dictionary<string, List<InlineFunctionInfo>> _functionDefinitions;
          private bool _includeInlineFunctions;
          private bool _resolveLeftMostUnqualifiedNameAsNamespaceOnly;
          
          /// <summary>
          /// Initializes TypeResolver instance
          /// </summary>
          internal TypeResolver(Perspective perspective, ParserOptions parserOptions)
          {
              EntityUtil.CheckArgumentNull(perspective, "perspective");
  
              _perspective = perspective;
              _parserOptions = parserOptions;
              _aliasedNamespaces = new Dictionary<string, MetadataNamespace>(parserOptions.NameComparer);
              _namespaces = new HashSet<MetadataNamespace>(MetadataMember.CreateMetadataMemberNameEqualityComparer(parserOptions.NameComparer));
              _functionDefinitions = new Dictionary<string, List<InlineFunctionInfo>>(parserOptions.NameComparer);
              _includeInlineFunctions = true;
              _resolveLeftMostUnqualifiedNameAsNamespaceOnly = false;
          }
  
          /// <summary>
          /// Returns perspective.
          /// </summary>
          internal Perspective Perspective
          {
              get { return _perspective; }
          }
  
          /// <summary>
          /// Returns namespace imports.
          /// </summary>
          internal ICollection<MetadataNamespace> NamespaceImports
          {
              get { return _namespaces; }
          }
  
          /// <summary>
          /// Returns <see cref="TypeUsage"/> for <see cref="PrimitiveTypeKind.String"/>.
          /// </summary>
          internal TypeUsage StringType
          {
              get { return _perspective.MetadataWorkspace.GetCanonicalModelTypeUsage(PrimitiveTypeKind.String); }
          }
  
          /// <summary>
          /// Returns <see cref="TypeUsage"/> for <see cref="PrimitiveTypeKind.Boolean"/>.
          /// </summary>
          internal TypeUsage BooleanType
          {
              get { return _perspective.MetadataWorkspace.GetCanonicalModelTypeUsage(PrimitiveTypeKind.Boolean); }
          }
  
          /// <summary>
          /// Returns <see cref="TypeUsage"/> for <see cref="PrimitiveTypeKind.Int64"/>.
          /// </summary>
          internal TypeUsage Int64Type
          {
              get { return _perspective.MetadataWorkspace.GetCanonicalModelTypeUsage(PrimitiveTypeKind.Int64); }
          }
  
          /// <summary>
          /// Adds an aliased namespace import.
          /// </summary>
          internal void AddAliasedNamespaceImport(string alias, MetadataNamespace @namespace, ErrorContext errCtx)
          {
              if (_aliasedNamespaces.ContainsKey(alias))
              {
                  throw EntityUtil.EntitySqlError(errCtx, Strings.NamespaceAliasAlreadyUsed(alias));
              }
              
              _aliasedNamespaces.Add(alias, @namespace);
          }
  
          /// <summary>
          /// Adds a non-aliased namespace import.
          /// </summary>
          internal void AddNamespaceImport(MetadataNamespace @namespace, ErrorContext errCtx)
          {
              if (_namespaces.Contains(@namespace))
              {
                  throw EntityUtil.EntitySqlError(errCtx, Strings.NamespaceAlreadyImported(@namespace.Name));
              }
  
              _namespaces.Add(@namespace);
          }
  
          #region Inline function declarations
          /// <summary>
          /// Declares inline function in the query local metadata.
          /// </summary>
          internal void DeclareInlineFunction(string name, InlineFunctionInfo functionInfo)
          {
              Debug.Assert(!String.IsNullOrEmpty(name), "name must not be null or empty");
              Debug.Assert(functionInfo != null, "functionInfo != null");
  
              List<InlineFunctionInfo> overloads;
              if (!_functionDefinitions.TryGetValue(name, out overloads))
              {
                  overloads = new List<InlineFunctionInfo>();
                  _functionDefinitions.Add(name, overloads);
              }
  
              //
              // Check overload uniqueness.
              //
              if (overloads.Exists(overload =>
                  overload.Parameters.Select(p => p.ResultType).SequenceEqual(functionInfo.Parameters.Select(p => p.ResultType), TypeUsageStructuralComparer.Instance)))
              {
                  throw EntityUtil.EntitySqlError(functionInfo.FunctionDefAst.ErrCtx, Strings.DuplicatedInlineFunctionOverload(name));
              }
  
              overloads.Add(functionInfo);
          }
  
          private sealed class TypeUsageStructuralComparer : IEqualityComparer<TypeUsage>
          {
              internal static readonly TypeUsageStructuralComparer Instance = new TypeUsageStructuralComparer();
  
              private TypeUsageStructuralComparer() { }
  
              public bool Equals(TypeUsage x, TypeUsage y)
              {
                  return TypeSemantics.IsStructurallyEqual(x, y);
              }
  
              public int GetHashCode(TypeUsage obj)
              {
                  Debug.Fail("Not implemented");
                  return 0;
              }
          }
          #endregion
  
          internal IDisposable EnterFunctionNameResolution(bool includeInlineFunctions)
          {
              bool savedIncludeInlineFunctions = _includeInlineFunctions;
              _includeInlineFunctions = includeInlineFunctions;
              return new Disposer(delegate { this._includeInlineFunctions = savedIncludeInlineFunctions; });
          }
  
          internal IDisposable EnterBackwardCompatibilityResolution()
          {
              Debug.Assert(!_resolveLeftMostUnqualifiedNameAsNamespaceOnly, "EnterBackwardCompatibilityResolution() is not reentrant.");
              _resolveLeftMostUnqualifiedNameAsNamespaceOnly = true;
              return new Disposer(delegate
              {
                  Debug.Assert(this._resolveLeftMostUnqualifiedNameAsNamespaceOnly, "_resolveLeftMostUnqualifiedNameAsNamespaceOnly must be true.");
                  this._resolveLeftMostUnqualifiedNameAsNamespaceOnly = false;
              });
          }
  
          internal MetadataMember ResolveMetadataMemberName(string[] name, ErrorContext errCtx)
          {
              Debug.Assert(name != null && name.Length > 0, "name must not be empty");
  
              MetadataMember metadataMember;
              if (name.Length == 1)
              {
                  metadataMember = ResolveUnqualifiedName(name[0], false /* partOfQualifiedName */, errCtx);
              }
              else
              {
                  metadataMember = ResolveFullyQualifiedName(name, name.Length, errCtx);
              }
              Debug.Assert(metadataMember != null, "metadata member name resolution must not return null");
  
              return metadataMember;
          }
  
          internal MetadataMember ResolveMetadataMemberAccess(MetadataMember qualifier, string name, ErrorContext errCtx)
          {
              string fullName = GetFullName(qualifier.Name, name);
              if (qualifier.MetadataMemberClass == MetadataMemberClass.Namespace)
              {
                  //
                  // Try resolving as a type.
                  //
                  MetadataType type;
                  if (TryGetTypeFromMetadata(fullName, out type))
                  {
                      return type;
                  }
  
                  //
                  // Try resolving as a function.
                  //
                  MetadataFunctionGroup function;
                  if (TryGetFunctionFromMetadata(qualifier.Name, name, out function))
                  {
                      return function;
                  }
  
                  //
                  // Otherwise, resolve as a namespace.
                  //
                  return new MetadataNamespace(fullName);
              }
              else if (qualifier.MetadataMemberClass == MetadataMemberClass.Type)
              {
                  var type = (MetadataType)qualifier;
                  if (TypeSemantics.IsEnumerationType(type.TypeUsage))
                  {
                      EnumMember member;
                      if (_perspective.TryGetEnumMember((EnumType)type.TypeUsage.EdmType, name, _parserOptions.NameComparisonCaseInsensitive /*ignoreCase*/, out member))
                      {
                          Debug.Assert(member != null, "member != null");
                          Debug.Assert(_parserOptions.NameComparer.Equals(name, member.Name), "_parserOptions.NameComparer.Equals(name, member.Name)");
                          return new MetadataEnumMember(fullName, type.TypeUsage, member);
                      }
                      else
                      {
                          throw EntityUtil.EntitySqlError(errCtx, Strings.NotAMemberOfType(name, qualifier.Name));
                      }
                  }
              }
  
              throw EntityUtil.EntitySqlError(errCtx, Strings.InvalidMetadataMemberClassResolution(
                  qualifier.Name, qualifier.MetadataMemberClassName, MetadataNamespace.NamespaceClassName));
          }
  
          internal MetadataMember ResolveUnqualifiedName(string name, bool partOfQualifiedName, ErrorContext errCtx)
          {
              Debug.Assert(!String.IsNullOrEmpty(name), "name must not be empty");
  
              //
              // In the case of Name1.Name2...NameN and if backward compatibility mode is on, then resolve Name1 as namespace only, ignore any other possible resolutions.
              //
              bool resolveAsNamespaceOnly = partOfQualifiedName && _resolveLeftMostUnqualifiedNameAsNamespaceOnly;
  
              //
              // In the case of Name1.Name2...NameN, ignore functions while resolving Name1: functions don't have members.
              //
              bool includeFunctions = !partOfQualifiedName;
  
              //
              // Try resolving as an inline function.
              //
              InlineFunctionGroup inlineFunctionGroup;
              if (!resolveAsNamespaceOnly && 
                  includeFunctions && TryGetInlineFunction(name, out inlineFunctionGroup))
              {
                  return inlineFunctionGroup;
              }
  
              //
              // Try resolving as a namespace alias.
              //
              MetadataNamespace aliasedNamespaceImport;
              if (_aliasedNamespaces.TryGetValue(name, out aliasedNamespaceImport))
              {
                  return aliasedNamespaceImport;
              }
  
              if (!resolveAsNamespaceOnly)
              {
                  //
                  // Try resolving as a type or functionGroup in the global namespace or as an imported member.
                  // Throw if ambiguous.
                  //
                  MetadataType type = null;
                  MetadataFunctionGroup functionGroup = null;
  
                  if (!TryGetTypeFromMetadata(name, out type))
                  {
                      if (includeFunctions)
                      {
                          //
                          // If name looks like a multipart identifier, try resolving it in the global namespace.
                          // Escaped multipart identifiers usually appear in views: select [NS1.NS2.Product](...) from ...
                          //
                          var multipart = name.Split('.');
                          if (multipart.Length > 1 && multipart.All(p => p.Length > 0))
                          {
                              var functionName = multipart[multipart.Length - 1];
                              var namespaceName = name.Substring(0, name.Length - functionName.Length - 1);
                              TryGetFunctionFromMetadata(namespaceName, functionName, out functionGroup);
                          }
                      }
                  }
  
                  //
                  // Try resolving as an imported member.
                  //
                  MetadataNamespace importedMemberNamespace = null;
                  foreach (MetadataNamespace namespaceImport in _namespaces)
                  {
                      string fullName = GetFullName(namespaceImport.Name, name);
  
                      MetadataType importedType;
                      if (TryGetTypeFromMetadata(fullName, out importedType))
                      {
                          if (type == null && functionGroup == null)
                          {
                              type = importedType;
                              importedMemberNamespace = namespaceImport;
                          }
                          else
                          {
                              throw AmbiguousMetadataMemberName(errCtx, name, namespaceImport, importedMemberNamespace);
                          }
                      }
  
                      MetadataFunctionGroup importedFunctionGroup;
                      if (includeFunctions && TryGetFunctionFromMetadata(namespaceImport.Name, name, out importedFunctionGroup))
                      {
                          if (type == null && functionGroup == null)
                          {
                              functionGroup = importedFunctionGroup;
                              importedMemberNamespace = namespaceImport;
                          }
                          else
                          {
                              throw AmbiguousMetadataMemberName(errCtx, name, namespaceImport, importedMemberNamespace);
                          }
                      }
                  }
                  if (type != null)
                  {
                      return type;
                  }
                  if (functionGroup != null)
                  {
                      return functionGroup;
                  }
              }
  
              //
              // Otherwise, resolve as a namespace.
              //
              return new MetadataNamespace(name);
          }
  
          private MetadataMember ResolveFullyQualifiedName(string[] name, int length, ErrorContext errCtx)
          {
              Debug.Assert(name != null && length > 1 && length <= name.Length, "name must not be empty");
  
              //
              // Resolve N in N.R
              //
              MetadataMember left;
              if (length == 2)
              {
                  //
                  // If N is a single name, ignore functions: functions don't have members.
                  //
                  left = ResolveUnqualifiedName(name[0], true /* partOfQualifiedName */, errCtx);
              }
              else
              {
                  left = ResolveFullyQualifiedName(name, length - 1, errCtx);
              }
  
              //
              // Get R in N.R
              //
              string rightName = name[length - 1];
              Debug.Assert(!String.IsNullOrEmpty(rightName), "rightName must not be empty");
  
              //
              // Resolve R in the context of N
              //
              return ResolveMetadataMemberAccess(left, rightName, errCtx);
          }
  
          private static Exception AmbiguousMetadataMemberName(ErrorContext errCtx, string name, MetadataNamespace ns1, MetadataNamespace ns2)
          {
              throw EntityUtil.EntitySqlError(errCtx, Strings.AmbiguousMetadataMemberName(name, ns1.Name, ns2 != null ? ns2.Name : null));
          }
  
          /// <summary>
          /// Try get type from the model using the fully qualified name.
          /// </summary>
          private bool TryGetTypeFromMetadata(string typeFullName, out MetadataType type)
          {
              TypeUsage typeUsage;
              if (_perspective.TryGetTypeByName(typeFullName, _parserOptions.NameComparisonCaseInsensitive /* ignore case */, out typeUsage))
              {
                  type = new MetadataType(typeFullName, typeUsage);
                  return true;
              }
              else
              {
                  type = null;
                  return false;
              }
          }
  
          /// <summary>
          /// Try get function from the model using the fully qualified name.
          /// </summary>
          internal bool TryGetFunctionFromMetadata(string namespaceName, string functionName, out MetadataFunctionGroup functionGroup)
          {
              IList<EdmFunction> functionMetadata;
              if (_perspective.TryGetFunctionByName(namespaceName, functionName, _parserOptions.NameComparisonCaseInsensitive /* ignore case */, out functionMetadata))
              {
                  functionGroup = new MetadataFunctionGroup(GetFullName(namespaceName, functionName), functionMetadata);
                  return true;
              }
              else
              {
                  functionGroup = null;
                  return false;
              }
          }
  
          /// <summary>
          /// Try get function from the local metadata using the fully qualified name.
          /// </summary>
          private bool TryGetInlineFunction(string functionName, out InlineFunctionGroup inlineFunctionGroup)
          {
              List<InlineFunctionInfo> inlineFunctionMetadata;
              if (_includeInlineFunctions && _functionDefinitions.TryGetValue(functionName, out inlineFunctionMetadata))
              {
                  inlineFunctionGroup = new InlineFunctionGroup(functionName, inlineFunctionMetadata);
                  return true;
              }
              else
              {
                  inlineFunctionGroup = null;
                  return false;
              }
          }
  
          /// <summary>
          /// Builds a dot-separated multipart identifier off the provided <paramref name="names"/>.
          /// </summary>
          internal static string GetFullName(params string[] names)
          {
              Debug.Assert(names != null && names.Length > 0, "names must not be null or empty");
              return String.Join(".", names);
          }
      }
  }
  