File: FormMapping\PrefixResolver.cs
Web Access
Project: src\src\Components\Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj (Microsoft.AspNetCore.Components.Endpoints)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
 
namespace Microsoft.AspNetCore.Components.Endpoints.FormMapping;
 
internal readonly struct PrefixResolver : IDisposable
{
    private readonly FormKey[] _sortedKeys;
    private readonly int _length;
 
    public bool HasValues => _length > 0 && _sortedKeys != null;
 
    public PrefixResolver(IEnumerable<FormKey> readOnlyMemoryKeys, int count)
    {
        _sortedKeys = ArrayPool<FormKey>.Shared.Rent(count);
        _length = count;
        var i = 0;
        foreach (var key in readOnlyMemoryKeys)
        {
            _sortedKeys[i++] = key;
        }
 
        Array.Sort(_sortedKeys, 0, count, FormKeyComparer.SortCriteria);
    }
 
    internal bool HasPrefix(ReadOnlyMemory<char> currentPrefixBuffer)
    {
        if (currentPrefixBuffer.Length == 0)
        {
            return _length > 0 && !(_length == 1 && _sortedKeys[0].Value.Length == 0);
        }
        return Array.BinarySearch(_sortedKeys, 0, _length, new FormKey(currentPrefixBuffer), FormKeyComparer.PrefixCriteria) >= 0;
    }
 
    public void Dispose()
    {
        if (_sortedKeys != null)
        {
            ArrayPool<FormKey>.Shared.Return(_sortedKeys);
        }
    }
 
    private class FormKeyComparer(bool checkPrefix) : IComparer<FormKey>
    {
        internal static readonly FormKeyComparer SortCriteria = new(checkPrefix: false);
        internal static readonly FormKeyComparer PrefixCriteria = new(checkPrefix: true);
 
        // When comparing values, y is the element we are trying to find.
        public int Compare(FormKey x, FormKey y)
        {
            var separatorX = 0;
            var separatorY = 0;
            var currentXPos = 0;
            var currentYPos = 0;
            while (separatorX != -1 && separatorY != -1)
            {
                separatorX = x.Value.Span[currentXPos..].IndexOfAny('.', '[');
                separatorY = y.Value.Span[currentYPos..].IndexOfAny('.', '[');
 
                if (separatorX == -1 && separatorY == -1)
                {
                    // no more segments, compare the remaining substrings
                    return MemoryExtensions.CompareTo(x.Value.Span[currentXPos..], y.Value.Span[currentYPos..], StringComparison.Ordinal);
                }
                else if (separatorX == -1)
                {
                    // x has no more segments, but y does, so x is less than y
                    return -1;
                }
                else if (separatorY == -1)
                {
                    if (!checkPrefix)
                    {
                        // We are just sorting, so x is greater than y because it has more segments.
                        return 1;
                    }
 
                    var match = MemoryExtensions.CompareTo(
                        x.Value.Span[currentXPos..][..separatorX],
                        y.Value.Span[currentYPos..], StringComparison.Ordinal);
 
                    return match;
                }
 
                // both have segments, compare the segments
                var segmentX = x.Value.Span[currentXPos..][..separatorX];
                var segmentY = y.Value.Span[currentYPos..][..separatorY];
                var compareResult = MemoryExtensions.CompareTo(segmentX, segmentY, StringComparison.Ordinal);
                if (compareResult != 0)
                {
                    return compareResult;
                }
 
                currentXPos += separatorX + 1;
                currentYPos += separatorY + 1;
            }
 
            return 0;
        }
    }
}