File: Collections\IdentifierCollection.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using System.Diagnostics;
 
namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// A dictionary that maps strings to all known spellings of that string. Can be used to
    /// efficiently store the set of known type names for a module for both VB and C# while also
    /// answering questions like "do you have a type called Goo" in either a case sensitive or
    /// insensitive manner.
    /// </summary>
    internal partial class IdentifierCollection
    {
        // Maps an identifier to all spellings of that identifier in this module.  The value type is
        // typed as object so that it can store either an individual element (the common case), or a 
        // collection.
        //
        // Note: we use a case insensitive comparer so that we can quickly lookup if we know a name
        // regardless of its case.
        private readonly Dictionary<string, object> _map = new Dictionary<string, object>(
            StringComparer.OrdinalIgnoreCase);
 
        public IdentifierCollection()
        {
        }
 
        public IdentifierCollection(IEnumerable<string> identifiers)
        {
            this.AddIdentifiers(identifiers);
        }
 
        public void AddIdentifiers(IEnumerable<string> identifiers)
        {
            foreach (var identifier in identifiers)
            {
                AddIdentifier(identifier);
            }
        }
 
        public void AddIdentifier(string identifier)
        {
            RoslynDebug.Assert(identifier != null);
 
            object? value;
            if (!_map.TryGetValue(identifier, out value))
            {
                AddInitialSpelling(identifier);
            }
            else
            {
                AddAdditionalSpelling(identifier, value);
            }
        }
 
        private void AddAdditionalSpelling(string identifier, object value)
        {
            // Had a mapping for it.  It will either map to a single 
            // spelling, or to a set of spellings.
            var strValue = value as string;
            if (strValue != null)
            {
                if (!string.Equals(identifier, strValue, StringComparison.Ordinal))
                {
                    // We now have two spellings.  Create a collection for
                    // that and map the name to it.
                    _map[identifier] = new HashSet<string> { identifier, strValue };
                }
            }
            else
            {
                // We have multiple spellings already.
                var spellings = (HashSet<string>)value;
 
                // Note: the set will prevent duplicates.
                spellings.Add(identifier);
            }
        }
 
        private void AddInitialSpelling(string identifier)
        {
            // We didn't have any spellings for this word already.  Just
            // add the word as the single known spelling.
            _map.Add(identifier, identifier);
        }
 
        public bool ContainsIdentifier(string identifier, bool caseSensitive)
        {
            RoslynDebug.Assert(identifier != null);
 
            if (caseSensitive)
            {
                return CaseSensitiveContains(identifier);
            }
            else
            {
                return CaseInsensitiveContains(identifier);
            }
        }
 
        private bool CaseInsensitiveContains(string identifier)
        {
            // Simple case.  Just check if we've mapped this word to 
            // anything.  The map will take care of the case insensitive
            // lookup for us.
            return _map.ContainsKey(identifier);
        }
 
        private bool CaseSensitiveContains(string identifier)
        {
            object? spellings;
            if (_map.TryGetValue(identifier, out spellings))
            {
                var spelling = spellings as string;
                if (spelling != null)
                {
                    return string.Equals(identifier, spelling, StringComparison.Ordinal);
                }
 
                var set = (HashSet<string>)spellings;
                return set.Contains(identifier);
            }
 
            return false;
        }
 
        public ICollection<string> AsCaseSensitiveCollection()
        {
            return new CaseSensitiveCollection(this);
        }
 
        public ICollection<string> AsCaseInsensitiveCollection()
        {
            return new CaseInsensitiveCollection(this);
        }
    }
}