File: Matching\DfaNode.cs
Web Access
Project: src\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Http;
 
namespace Microsoft.AspNetCore.Routing.Matching;
 
// Intermediate data structure used to build the DFA. Not used at runtime.
[DebuggerDisplay("{DebuggerToString(),nq}")]
internal sealed class DfaNode
{
    // The depth of the node. The depth indicates the number of segments
    // that must be processed to arrive at this node.
    //
    // This value is not computed for Policy nodes and will be set to -1.
    public int PathDepth { get; set; } = -1;
 
    // Just for diagnostics and debugging
    public string Label { get; set; }
 
    public List<Endpoint> Matches { get; private set; }
 
    public Dictionary<string, DfaNode> Literals { get; private set; }
 
    public DfaNode Parameters { get; set; }
 
    public DfaNode CatchAll { get; set; }
 
    public INodeBuilderPolicy NodeBuilder { get; set; }
 
    public Dictionary<object, DfaNode> PolicyEdges { get; private set; }
 
    public void AddPolicyEdge(object state, DfaNode node)
    {
        if (PolicyEdges == null)
        {
            PolicyEdges = new Dictionary<object, DfaNode>();
        }
 
        PolicyEdges.Add(state, node);
    }
 
    public void AddLiteral(string literal, DfaNode node)
    {
        if (Literals == null)
        {
            Literals = new Dictionary<string, DfaNode>(StringComparer.OrdinalIgnoreCase);
        }
 
        Literals.Add(literal, node);
    }
 
    public void AddMatch(Endpoint endpoint)
    {
        if (Matches == null)
        {
            Matches = new List<Endpoint>();
        }
 
        Matches.Add(endpoint);
    }
 
    public void AddMatches(IEnumerable<Endpoint> endpoints)
    {
        if (Matches == null)
        {
            Matches = new List<Endpoint>(endpoints);
        }
        else
        {
            Matches.AddRange(endpoints);
        }
    }
 
    public void Visit(Action<DfaNode> visitor)
    {
        if (Literals != null)
        {
            foreach (var kvp in Literals)
            {
                kvp.Value.Visit(visitor);
            }
        }
 
        // Break cycles
        if (Parameters != null && !ReferenceEquals(this, Parameters))
        {
            Parameters.Visit(visitor);
        }
 
        // Break cycles
        if (CatchAll != null && !ReferenceEquals(this, CatchAll))
        {
            CatchAll.Visit(visitor);
        }
 
        if (PolicyEdges != null)
        {
            foreach (var kvp in PolicyEdges)
            {
                kvp.Value.Visit(visitor);
            }
        }
 
        visitor(this);
    }
 
    private string DebuggerToString()
    {
        var builder = new StringBuilder();
        builder.Append(Label);
        builder.Append(" d:");
        builder.Append(PathDepth);
        builder.Append(" m:");
        builder.Append(Matches?.Count ?? 0);
        builder.Append(" c: ");
        if (Literals != null)
        {
            builder.AppendJoin(", ", Literals.Select(kvp => $"{kvp.Key}->({FormatNode(kvp.Value)})"));
        }
        return builder.ToString();
 
        // DfaNodes can be self-referential, don't traverse cycles.
        string FormatNode(DfaNode other)
        {
            return ReferenceEquals(this, other) ? "this" : other.DebuggerToString();
        }
    }
}