diff --git a/gts/gts.csproj b/gts/gts.csproj index a1619adb..36f1a0aa 100644 --- a/gts/gts.csproj +++ b/gts/gts.csproj @@ -3539,6 +3539,7 @@ RoomLeaders.aspx + @@ -3550,6 +3551,7 @@ + diff --git a/gts/src/DependencyNode.cs b/gts/src/DependencyNode.cs new file mode 100644 index 00000000..8a44733e --- /dev/null +++ b/gts/src/DependencyNode.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace PkmnFoundations.Web +{ + public class DependencyNode + { + public DependencyNode() + { + Dependencies = new HashSet(); + } + + public DependencyNode(TKey key, TValue value) + : this() + { + Key = key; + Value = value; + } + + public DependencyNode(TKey key, TValue value, HashSet dependencies) + : this(key, value) + { + Dependencies = dependencies; + } + + public TKey Key { get; set; } + public TValue Value { get; set; } + public HashSet Dependencies { get; private set; } + } +} diff --git a/gts/src/RequireLinkBase.cs b/gts/src/RequireLinkBase.cs new file mode 100644 index 00000000..878ad443 --- /dev/null +++ b/gts/src/RequireLinkBase.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.UI; +using System.Web.UI.HtmlControls; + +namespace PkmnFoundations.Web +{ + public abstract class RequireLinkBase : System.Web.UI.Control + { + protected RequireLinkBase() + : base() + { + this.Init += RequireLinkBase_Init; + this.Load += RequireLinkBase_Load; + this.PreRender += RequireLinkBase_PreRender; + } + + void RequireLinkBase_Init(object sender, EventArgs e) + { + // todo: only bind once + Page.PreRender += Page_PreRender; + } + + void RequireLinkBase_Load(object sender, EventArgs e) + { + } + + void RequireLinkBase_PreRender(object sender, EventArgs e) + { + DependencyGraph graph = GetDependencyGraph(); + + DependencyNode node = new DependencyNode(Key, this, ParseDependencies(After ?? "")); + if (!graph.Graph.Any(n => n.Key == Key)) + graph.Graph.Add(node); + } + + void Page_PreRender(object sender, EventArgs e) + { + DependencyGraph graph = GetDependencyGraph(); + if (graph.Page == null) + { + Page.Header.Controls.Add(new RequireLinkRenderer(GetDependencyGraph())); + graph.Page = Page; + } + } + + public String Key { get; set; } + public String After { get; set; } + + public abstract void RenderHeader(System.Web.UI.HtmlTextWriter writer); + + /// + /// Obtain a dependency graph which is specific to the Page instance + /// and the control class + /// + private DependencyGraph GetDependencyGraph() + { + Type myType = this.GetType(); + + Dictionary> all_graphs; + + if (!Page.Items.Contains("pkmncfDependencyGraphs")) + { + all_graphs = new Dictionary>(); + Page.Items.Add("pkmncfDependencyGraphs", all_graphs); + } + else all_graphs = + (Dictionary>)Page.Items["pkmncfDependencyGraphs"]; + + if (all_graphs.ContainsKey(myType)) return all_graphs[myType]; + + DependencyGraph myGraph = new DependencyGraph(); + all_graphs.Add(myType, myGraph); + return myGraph; + } + + private HashSet ParseDependencies(String dependencies) + { + return new HashSet(dependencies.Split(',').Select(s => s.Trim()).Where(s => s.Length > 0)); + } + + } + + internal class RequireLinkRenderer : System.Web.UI.Control + { + public RequireLinkRenderer(DependencyGraph graph) + : base() + { + m_graph = graph; + } + + private DependencyGraph m_graph; + + protected override void Render(System.Web.UI.HtmlTextWriter writer) + { + List links = m_graph.Resolve(); + + foreach (RequireLinkBase link in links) + link.RenderHeader(writer); + } + } + + internal class DependencyGraph + { + public DependencyGraph() + { + Graph = new List>(); + } + + public DependencyGraph(List> graph) + { + Graph = graph; + } + + public List> Graph; + public Page Page = null; + + public List Resolve() + { + // fixme: we should clone the DependencyNodes so they don't get modified. + LinkedList> nodesList = new LinkedList>(Graph); + List result = new List(nodesList.Count); + + while (nodesList.Count > 0) + { + LinkedListNode> nodeLinked = nodesList.First; + + while (nodeLinked != null) + { + if (nodeLinked.Value.Dependencies.Count == 0) break; + nodeLinked = nodeLinked.Next; + } + + if (nodeLinked == null) + { + // we hit the end of the list without finding a dependency-less node. + // this means there has to be a circular dependency. + // ie. every remaining node in the list depends on some other remaining + // node in the list. + + // todo: display the actual cycle (or an example if there's more than one) + // in this exception. + // algo: + // Keep a collection of found nodes. + // Start at the first node, adding it to the collection. + // Move to the node of the first dependency of this node and add it to the collection. + // Repeat until you reach a node which is already in the collection. + // Output the collection of nodes, starting at the node which was already found. + throw new Exception("Circular dependency found in your links.\n" + + "Keys:\n" + + String.Join("\n", nodesList.Select(n => n.Key).ToArray())); + } + + // add this node to output, remove it as a dependency from the remaining nodes + DependencyNode nodeOutput = nodeLinked.Value; + nodesList.Remove(nodeLinked); + + foreach (DependencyNode nodeValue in nodesList) + nodeValue.Dependencies.Remove(nodeOutput.Key); + + result.Add(nodeOutput.Value); + } + return result; + } + } +}