using System; using System.Collections.Generic; namespace Umbraco.Core.Collections { public class TopoGraph { internal const string CycleDependencyError = "Cyclic dependency."; internal const string MissingDependencyError = "Missing dependency."; public class Node { public Node(TKey key, TItem item, IEnumerable dependencies) { Key = key; Item = item; Dependencies = dependencies; } public TKey Key { get; } public TItem Item { get; } public IEnumerable Dependencies { get; } } public static Node CreateNode(TKey key, TItem item, IEnumerable dependencies) => new Node(key, item, dependencies); } /// /// 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); } } } }