File: Matching\Trie.cs
Web Access
Project: Microsoft.TemplateEngine.Core.csproj (Microsoft.TemplateEngine.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.TemplateEngine.Core.Matching
{
    public class Trie<T>
        where T : TerminalBase
    {
        private byte _singleNextNodeMatch;
        private TrieNode<T>? _singleNextNode;
        private Dictionary<byte, TrieNode<T>>? _nextNodes;

        public Trie()
        {
        }

        public Dictionary<byte, TrieNode<T>> NextNodes
        {
            get
            {
                if (_nextNodes is { } nextNodes)
                {
                    return nextNodes;
                }

                nextNodes = new Dictionary<byte, TrieNode<T>>();
                if (_singleNextNode != null)
                {
                    nextNodes.Add(_singleNextNodeMatch, _singleNextNode);
                }

                return Interlocked.CompareExchange(ref _nextNodes, nextNodes, null) ?? nextNodes;
            }
        }

        public int MaxRemainingLength { get; private set; }

        public void AddPath(byte[] path, T terminal)
        {
            if (path.Length > MaxRemainingLength)
            {
                MaxRemainingLength = path.Length;
            }

            int remainingLength = path.Length - 1;
            Trie<T> current = this;
            for (int i = 0; i < path.Length; ++i, --remainingLength)
            {
                TrieNode<T> next = current.GetOrAddNextNode(path[i], remainingLength);

                if (i == path.Length - 1)
                {
                    next.Terminals ??= new List<T>();

                    int sameMatcherIndex = next.Terminals.FindIndex(t => t.Start == terminal.Start && t.End == terminal.End);

                    if (sameMatcherIndex > -1)
                    {
                        // this matching is identical to another terminal already added to the trie. Overwrite it.
                        next.Terminals[sameMatcherIndex] = terminal;
                    }
                    else
                    {
                        next.Terminals.Add(terminal);
                    }
                }

                current = next;
            }
        }

        internal bool TryGetNextNode(byte match, out TrieNode<T> next)
        {
            if (_nextNodes != null)
            {
                return _nextNodes.TryGetValue(match, out next!);
            }

            if (_singleNextNode != null && _singleNextNodeMatch == match)
            {
                next = _singleNextNode;
                return true;
            }

            next = null!;
            return false;
        }

        private TrieNode<T> GetOrAddNextNode(byte match, int remainingLength)
        {
            TrieNode<T> next;
            if (_nextNodes != null)
            {
                if (!_nextNodes.TryGetValue(match, out next!))
                {
                    _nextNodes.Add(match, next = new TrieNode<T>(match));
                }
            }
            else if (_singleNextNode == null)
            {
                _singleNextNodeMatch = match;
                _singleNextNode = next = new TrieNode<T>(match);
            }
            else if (_singleNextNodeMatch == match)
            {
                next = _singleNextNode;
            }
            else
            {
                next = new TrieNode<T>(match);
                NextNodes.Add(match, next);
            }

            if (next.MaxRemainingLength < remainingLength)
            {
                next.MaxRemainingLength = remainingLength;
            }

            return next;
        }
    }
}