using System;
using System.Collections.Generic;
namespace Umbraco.Core
{
public class TopoGraph
{
internal const string CycleDependencyError = "Cyclic dependency.";
internal const string MissingDependencyError = "Missing dependency.";
}
///
/// Represents a generic DAG that can be topologically sorted.
///
/// The type of the keys.
/// The type of the items.
public class TopoGraph : TopoGraph
{
private readonly Func _getKey;
private readonly Func> _getDependencies;
private readonly Dictionary _items = new Dictionary();
///
/// Initializes a new instance of the class.
///
/// A method that returns the key of an item.
/// A method that returns the dependency keys of an item.
public TopoGraph(Func getKey, Func> getDependencies)
{
_getKey = getKey;
_getDependencies = getDependencies;
}
///
/// Adds an item to the graph.
///
/// The item.
public void AddItem(TItem item)
{
var key = _getKey(item);
_items[key] = item;
}
///
/// Adds items to the graph.
///
/// The items.
public void AddItems(IEnumerable items)
{
foreach (var item in items)
AddItem(item);
}
///
/// Gets the sorted items.
///
/// A value indicating whether to throw on cycles, or just ignore the branch.
/// A value indicating whether to throw on missing dependency, or just ignore the dependency.
/// A value indicating whether to reverse the order.
/// The (topologically) sorted items.
public IEnumerable GetSortedItems(bool throwOnCycle = true, bool throwOnMissing = true, bool reverse = false)
{
var sorted = new TItem[_items.Count];
var visited = new HashSet();
var index = reverse ? _items.Count - 1 : 0;
var incr = reverse ? -1 : +1;
foreach (var item in _items.Values)
Visit(item, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing);
return sorted;
}
private static bool Contains(TItem[] items, TItem item, int start, int count)
{
return Array.IndexOf(items, item, start, count) >= 0;
}
private void Visit(TItem item, ISet visited, TItem[] sorted, ref int index, int incr, bool throwOnCycle, bool throwOnMissing)
{
if (visited.Contains(item))
{
// visited but not sorted yet = cycle
var start = incr > 0 ? 0 : index;
var count = incr > 0 ? index : sorted.Length - index;
if (throwOnCycle && Contains(sorted, item, start, count) == false)
throw new Exception(CycleDependencyError);
return;
}
visited.Add(item);
var keys = _getDependencies(item);
var dependencies = keys == null ? null : FindDependencies(keys, throwOnMissing);
if (dependencies != null)
foreach (var dep in dependencies)
Visit(dep, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing);
sorted[index] = item;
index += incr;
}
private IEnumerable FindDependencies(IEnumerable keys, bool throwOnMissing)
{
foreach (var key in keys)
{
TItem value;
if (_items.TryGetValue(key, out value))
yield return value;
else if (throwOnMissing)
throw new Exception(MissingDependencyError);
}
}
}
}