// Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel;
using System.Diagnostics;
using Microsoft.AspNet.Mvc.Internal.DecisionTree;
namespace Microsoft.AspNet.Mvc.Routing
{
///
public class ActionSelectionDecisionTree : IActionSelectionDecisionTree
{
private readonly DecisionTreeNode _root;
///
/// Creates a new .
///
/// The .
public ActionSelectionDecisionTree(ActionDescriptorsCollection actions)
{
Version = actions.Version;
_root = DecisionTreeBuilder.GenerateTree(
actions.Items,
new ActionDescriptorClassifier());
}
///
public int Version { get; private set; }
///
public IReadOnlyList Select(IDictionary routeValues)
{
var results = new List();
Walk(results, routeValues, _root);
// If we have a match that isn't using catch-all, then it's considered better than matches with catch all
// so filter those out.
var hasNonCatchAll = false;
// The common case for MVC has no catch-alls, so avoid allocating.
List filtered = null;
foreach (var action in results)
{
var actionHasCatchAll = false;
if (action.RouteConstraints != null)
{
foreach (var constraint in action.RouteConstraints)
{
if (constraint.KeyHandling == RouteKeyHandling.CatchAll)
{
actionHasCatchAll = true;
break;
}
}
}
if (hasNonCatchAll && actionHasCatchAll)
{
// Do nothing - we've already found a better match.
}
else if (actionHasCatchAll)
{
if (filtered == null)
{
filtered = new List();
}
filtered.Add(action);
}
else if (hasNonCatchAll)
{
Debug.Assert(filtered != null);
filtered.Add(action);
}
else
{
// This is the first non-catch-all we've found.
hasNonCatchAll = true;
if (filtered == null)
{
filtered = new List();
}
else
{
filtered.Clear();
}
filtered.Add(action);
}
}
return filtered ?? results;
}
private void Walk(
List results,
IDictionary routeValues,
DecisionTreeNode node)
{
for (var i = 0; i < node.Matches.Count; i++)
{
results.Add(node.Matches[i]);
}
for (var i = 0; i < node.Criteria.Count; i++)
{
var criterion = node.Criteria[i];
var key = criterion.Key;
object value;
var hasValue = routeValues.TryGetValue(key, out value);
DecisionTreeNode branch;
if (criterion.Branches.TryGetValue(value ?? string.Empty, out branch))
{
Walk(results, routeValues, branch);
}
// If there's a fallback node we always need to process it when we have a value. We'll prioritize
// non-fallback matches later in the process.
if (hasValue && criterion.Fallback != null)
{
Walk(results, routeValues, criterion.Fallback);
}
}
}
private class ActionDescriptorClassifier : IClassifier
{
public ActionDescriptorClassifier()
{
ValueComparer = new RouteValueEqualityComparer();
}
public IEqualityComparer