public LinkGenerationDecisionTree(IReadOnlyList <OutboundMatch> entries)
        {
            // We split up the entries into:
            // 1. attribute routes - these go into the tree
            // 2. conventional routes - these are a list
            var attributedEntries = new List <OutboundMatch>();

            _conventionalEntries = new List <OutboundMatch>();

            // Anything with a RoutePattern.RequiredValueAny as a RequiredValue is a conventional route.
            // This is because RequiredValueAny acts as a wildcard, whereas an attribute route entry
            // is denormalized to contain an exact set of required values.
            //
            // We will only see conventional routes show up here for endpoint routing.
            for (var i = 0; i < entries.Count; i++)
            {
                var isAttributeRoute = true;
                var entry            = entries[i];
                foreach (var kvp in entry.Entry.RequiredLinkValues)
                {
                    if (RoutePattern.IsRequiredValueAny(kvp.Value))
                    {
                        isAttributeRoute = false;
                        break;
                    }
                }

                if (isAttributeRoute)
                {
                    attributedEntries.Add(entry);
                }
                else
                {
                    _conventionalEntries.Add(entry);
                }
            }

            _root = DecisionTreeBuilder <OutboundMatch> .GenerateTree(
                attributedEntries,
                new OutboundMatchClassifier());
        }
    public override RoutePattern SubstituteRequiredValues(RoutePattern original, RouteValueDictionary requiredValues)
    {
        if (original is null)
        {
            throw new ArgumentNullException(nameof(original));
        }

        // Process each required value in sequence. Bail if we find any rejection criteria. The goal
        // of rejection is to avoid creating RoutePattern instances that can't *ever* match.
        //
        // If we succeed, then we need to create a new RoutePattern with the provided required values.
        //
        // Substitution can merge with existing RequiredValues already on the RoutePattern as long
        // as all of the success criteria are still met at the end.
        foreach (var kvp in requiredValues)
        {
            // There are three possible cases here:
            // 1. Required value is null-ish
            // 2. Required value is *any*
            // 3. Required value corresponds to a parameter
            // 4. Required value corresponds to a matching default value
            //
            // If none of these are true then we can reject this substitution.
            RoutePatternParameterPart parameter;
            if (RouteValueEqualityComparer.Default.Equals(kvp.Value, string.Empty))
            {
                // 1. Required value is null-ish - check to make sure that this route doesn't have a
                // parameter or filter-like default.

                if (original.GetParameter(kvp.Key) != null)
                {
                    // Fail: we can't 'require' that a parameter be null. In theory this would be possible
                    // for an optional parameter, but that's not really in line with the usage of this feature
                    // so we don't handle it.
                    //
                    // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = "" }
                    return(null);
                }
                else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
                         !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
                {
                    // Fail: this route has a non-parameter default that doesn't match.
                    //
                    // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = "" }
                    return(null);
                }

                // Success: (for this parameter at least)
                //
                // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
                continue;
            }
            else if (RoutePattern.IsRequiredValueAny(kvp.Value))
            {
                // 2. Required value is *any* - this is allowed for a parameter with a default, but not
                // a non-parameter default.
                if (original.GetParameter(kvp.Key) == null &&
                    original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
                    !RouteValueEqualityComparer.Default.Equals(string.Empty, defaultValue))
                {
                    // Fail: this route as a non-parameter default that is stricter than *any*.
                    //
                    // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = *any* }
                    return(null);
                }

                // Success: (for this parameter at least)
                //
                // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = *any*, ... }
                continue;
            }
            else if ((parameter = original.GetParameter(kvp.Key)) != null)
            {
                // 3. Required value corresponds to a parameter - check to make sure that this value matches
                // any IRouteConstraint implementations.
                if (!MatchesConstraints(original, parameter, kvp.Key, requiredValues))
                {
                    // Fail: this route has a constraint that failed.
                    //
                    // Ex: Admin/{controller:regex(Home|Login)}/{action=Index}/{id?} - with required values: { controller = "Store" }
                    return(null);
                }

                // Success: (for this parameter at least)
                //
                // Ex: {area}/{controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
                continue;
            }
            else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
                     RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
            {
                // 4. Required value corresponds to a matching default value - check to make sure that this value matches
                // any IRouteConstraint implementations. It's unlikely that this would happen in practice but it doesn't
                // hurt for us to check.
                if (!MatchesConstraints(original, parameter: null, kvp.Key, requiredValues))
                {
                    // Fail: this route has a constraint that failed.
                    //
                    // Ex:
                    //  Admin/Home/{action=Index}/{id?}
                    //  defaults: { area = "Admin" }
                    //  constraints: { area = "Blog" }
                    //  with required values: { area = "Admin" }
                    return(null);
                }

                // Success: (for this parameter at least)
                //
                // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Admin", ... }
                continue;
            }
            else
            {
                // Fail: this is a required value for a key that doesn't appear in the templates, or the route
                // pattern has a different default value for a non-parameter.
                //
                // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Blog", ... }
                // OR (less likely)
                // Ex: Admin/{controller=Home}/{action=Index}/{id?} with required values: { page = "/Index", ... }
                return(null);
            }
        }

        List <RoutePatternParameterPart> updatedParameters = null;
        List <RoutePatternPathSegment>   updatedSegments   = null;
        RouteValueDictionary             updatedDefaults   = null;

        // So if we get here, we're ready to update the route pattern. We need to update two things:
        // 1. Remove any default values that conflict with the required values.
        // 2. Merge any existing required values
        foreach (var kvp in requiredValues)
        {
            var parameter = original.GetParameter(kvp.Key);

            // We only need to handle the case where the required value maps to a parameter. That's the only
            // case where we allow a default and a required value to disagree, and we already validated the
            // other cases.
            //
            // If the required value is *any* then don't remove the default.
            if (parameter != null &&
                !RoutePattern.IsRequiredValueAny(kvp.Value) &&
                original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
                !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
            {
                if (updatedDefaults == null && updatedSegments == null && updatedParameters == null)
                {
                    updatedDefaults   = new RouteValueDictionary(original.Defaults);
                    updatedSegments   = new List <RoutePatternPathSegment>(original.PathSegments);
                    updatedParameters = new List <RoutePatternParameterPart>(original.Parameters);
                }

                updatedDefaults.Remove(kvp.Key);
                RemoveParameterDefault(updatedSegments, updatedParameters, parameter);
            }
        }

        foreach (var kvp in original.RequiredValues)
        {
            requiredValues.TryAdd(kvp.Key, kvp.Value);
        }

        return(new RoutePattern(
                   original.RawText,
                   updatedDefaults ?? original.Defaults,
                   original.ParameterPolicies,
                   requiredValues,
                   updatedParameters ?? original.Parameters,
                   updatedSegments ?? original.PathSegments));
    }