aspnetcore/src/Microsoft.AspNetCore.Routing/Matching/EndpointComparer.cs

164 lines
5.2 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching
{
// Use to sort and group Endpoints. RouteEndpoints are sorted before other implementations.
//
// NOTE:
// When ordering endpoints, we compare the route templates as an absolute last resort.
// This is used as a factor to ensure that we always have a predictable ordering
// for tests, errors, etc.
//
// When we group endpoints we don't consider the route template, because we're trying
// to group endpoints not separate them.
//
// TLDR:
// IComparer implementation considers the template string as a tiebreaker.
// IEqualityComparer implementation does not.
// This is cool and good.
internal class EndpointComparer : IComparer<Endpoint>, IEqualityComparer<Endpoint>
{
private readonly IComparer<Endpoint>[] _comparers;
public EndpointComparer(IEndpointComparerPolicy[] policies)
{
// Order, Precedence, (others)...
_comparers = new IComparer<Endpoint>[2 + policies.Length];
_comparers[0] = OrderComparer.Instance;
_comparers[1] = PrecedenceComparer.Instance;
for (var i = 0; i < policies.Length; i++)
{
_comparers[i + 2] = policies[i].Comparer;
}
}
public int Compare(Endpoint x, Endpoint y)
{
// We don't expose this publicly, and we should never call it on
// a null endpoint.
Debug.Assert(x != null);
Debug.Assert(y != null);
var compare = CompareCore(x, y);
// Since we're sorting, use the route template as a last resort.
return compare == 0 ? ComparePattern(x, y) : compare;
}
private int ComparePattern(Endpoint x, Endpoint y)
{
// A RouteEndpoint always comes before a non-RouteEndpoint, regardless of its RawText value
var routeEndpointX = x as RouteEndpoint;
var routeEndpointY = y as RouteEndpoint;
if (routeEndpointX != null)
{
if (routeEndpointY != null)
{
return routeEndpointX.RoutePattern.RawText.CompareTo(routeEndpointY.RoutePattern.RawText);
}
return 1;
}
else if (routeEndpointY != null)
{
return -1;
}
return 0;
}
public bool Equals(Endpoint x, Endpoint y)
{
// We don't expose this publicly, and we should never call it on
// a null endpoint.
Debug.Assert(x != null);
Debug.Assert(y != null);
return CompareCore(x, y) == 0;
}
public int GetHashCode(Endpoint obj)
{
// This should not be possible to call publicly.
Debug.Fail("We don't expect this to be called.");
throw new System.NotImplementedException();
}
private int CompareCore(Endpoint x, Endpoint y)
{
for (var i = 0; i < _comparers.Length; i++)
{
var compare = _comparers[i].Compare(x, y);
if (compare != 0)
{
return compare;
}
}
return 0;
}
private class OrderComparer : IComparer<Endpoint>
{
public static readonly IComparer<Endpoint> Instance = new OrderComparer();
public int Compare(Endpoint x, Endpoint y)
{
var routeEndpointX = x as RouteEndpoint;
var routeEndpointY = y as RouteEndpoint;
if (routeEndpointX != null)
{
if (routeEndpointY != null)
{
return routeEndpointX.Order.CompareTo(routeEndpointY.Order);
}
return 1;
}
else if (routeEndpointY != null)
{
return -1;
}
return 0;
}
}
private class PrecedenceComparer : IComparer<Endpoint>
{
public static readonly IComparer<Endpoint> Instance = new PrecedenceComparer();
public int Compare(Endpoint x, Endpoint y)
{
var routeEndpointX = x as RouteEndpoint;
var routeEndpointY = y as RouteEndpoint;
if (routeEndpointX != null)
{
if (routeEndpointY != null)
{
return routeEndpointX.RoutePattern.InboundPrecedence
.CompareTo(routeEndpointY.RoutePattern.InboundPrecedence);
}
return 1;
}
else if (routeEndpointY != null)
{
return -1;
}
return 0;
}
}
}
}