PageRenderTime 265ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/Bistro.Core/Controllers/DependencyHelper.cs

http://bistro-framework.googlecode.com/
C# | 217 lines | 109 code | 23 blank | 85 comment | 11 complexity | f9f7cd9512cfeaa28f862df0ae445faf MD5 | raw file
Possible License(s): LGPL-3.0
  1. /****************************************************************************
  2. *
  3. * Bistro Framework Copyright © 2003-2009 Hill30 Inc
  4. *
  5. * This file is part of Bistro Framework.
  6. *
  7. * Bistro Framework is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * Bistro Framework is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License
  18. * along with Bistro Framework. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. ***************************************************************************/
  21. using System;
  22. using System.Collections.Generic;
  23. using System.Text;
  24. using Bistro.Controllers.Descriptor;
  25. namespace Bistro.Controllers
  26. {
  27. /// <summary>
  28. /// Assists in ordering and enforcing dependencies. This class is reusable, but not thread-safe
  29. /// </summary>
  30. class DependencyHelper
  31. {
  32. /// <summary>
  33. /// A mapping of context values to lists of controllers that require them
  34. /// </summary>
  35. Dictionary<string, List<ControllerInvocationInfo>> requirements = new Dictionary<string, List<ControllerInvocationInfo>>();
  36. /// <summary>
  37. /// A mapping of context values to lists of controllers that depend on them
  38. /// </summary>
  39. Dictionary<string, List<ControllerInvocationInfo>> dependencies = new Dictionary<string, List<ControllerInvocationInfo>>();
  40. /// <summary>
  41. /// A mapping of context values to lists of controllers that provide on them
  42. /// </summary>
  43. Dictionary<string, List<ControllerInvocationInfo>> providers = new Dictionary<string, List<ControllerInvocationInfo>>();
  44. /// <summary>
  45. /// used to track the structure we're building out, so that if we move something, we move everything it needs
  46. /// </summary>
  47. Dictionary<ControllerInvocationInfo, List<ControllerInvocationInfo>> finalDependencies = new Dictionary<ControllerInvocationInfo, List<ControllerInvocationInfo>>();
  48. /// <summary>
  49. /// retrieves the list under the requested key. if the key isn't present, the list is
  50. /// created and added
  51. /// </summary>
  52. /// <param name="key"></param>
  53. /// <param name="map"></param>
  54. /// <returns></returns>
  55. private List<ControllerInvocationInfo> getAndCreateList(string key, Dictionary<string, List<ControllerInvocationInfo>> map)
  56. {
  57. List<ControllerInvocationInfo> res = null;
  58. if (map.TryGetValue(key, out res))
  59. return res;
  60. res = new List<ControllerInvocationInfo>();
  61. map.Add(key, res);
  62. return res;
  63. }
  64. /// <summary>
  65. /// Gets the and create list.
  66. /// </summary>
  67. /// <param name="key">The key.</param>
  68. /// <param name="map">The map.</param>
  69. /// <returns></returns>
  70. private List<ControllerInvocationInfo> getAndCreateList(ControllerInvocationInfo key, Dictionary<ControllerInvocationInfo, List<ControllerInvocationInfo>> map)
  71. {
  72. List<ControllerInvocationInfo> res = null;
  73. if (map.TryGetValue(key, out res))
  74. return res;
  75. res = new List<ControllerInvocationInfo>();
  76. map.Add(key, res);
  77. return res;
  78. }
  79. /// <summary>
  80. /// reorders the list of controllers so that parameter dependencies are met.
  81. /// this implementation is simplistic, and does not look for cyclical dependencies.
  82. /// </summary>
  83. /// <param name="after"></param>
  84. internal void EnforceDependencies(List<ControllerInvocationInfo> after)
  85. {
  86. populateMaps(after);
  87. process(requirements, true);
  88. process(dependencies, false);
  89. sort(after, 0);
  90. }
  91. /// <summary>
  92. /// Sorts the supplied list based on finalDependencies. The method checks each element
  93. /// of the list against the keys of finalDependencies. If a match occurs, it then
  94. /// makes sure that the controller is earlier in the list that the contents of the key
  95. /// </summary>
  96. /// <param name="after"></param>
  97. private void sort(List<ControllerInvocationInfo> after, int iterationCount)
  98. {
  99. if (iterationCount > after.Count)
  100. {
  101. StringBuilder sb = new StringBuilder();
  102. foreach (ControllerInvocationInfo info in finalDependencies.Keys)
  103. {
  104. sb.Append("\r\n").Append(info.BindPoint.Controller.ControllerType.Name).Append(" is a required/requested resource for");
  105. foreach (ControllerInvocationInfo dep in finalDependencies[info])
  106. sb.Append("\r\n\t").Append(dep.BindPoint.Controller.ControllerType.Name);
  107. }
  108. sb.Insert(0, "Possible cyclical dependency detected:\r\n");
  109. throw new ApplicationException(sb.ToString());
  110. }
  111. int i = -1;
  112. bool resort = false;
  113. while (++i < after.Count)
  114. {
  115. List<ControllerInvocationInfo> dependents;
  116. if (!finalDependencies.TryGetValue(after[i], out dependents))
  117. continue;
  118. foreach (ControllerInvocationInfo dep in dependents)
  119. {
  120. int index = after.IndexOf(dep);
  121. if (index < i)
  122. {
  123. // it's possible to be your own dependent. you may modify
  124. // an inbound value, so you're both a dependent and a provider
  125. if (dep == after[i])
  126. continue;
  127. after.RemoveAt(index);
  128. after.Insert(i, dep);
  129. //the insert effectively moves this item down in the list
  130. //TODO: AP this needs to be reviewed. original unit test didn't catch this
  131. // with the addition of a standard "payload" controller, i started seeing
  132. // the payload controller bumped up higher than it should be. seems to be
  133. // related to this, though i'm not quite sure yet.
  134. /*if (i != (after.Count - 1))
  135. {
  136. i++;
  137. }*/
  138. resort = true;
  139. }
  140. }
  141. }
  142. // to make sure that indirect dependencies are addressed
  143. // we need to have at least one clean sort pass
  144. if (resort)
  145. sort(after, iterationCount + 1);
  146. }
  147. /// <summary>
  148. /// Ensures that all controllers given are satisfied by the contents of providers. As one or more
  149. /// providers of the same value are given, they are loaded into the finalDependencies list.
  150. /// </summary>
  151. /// <param name="requirements"></param>
  152. /// <param name="failOnNotMet"></param>
  153. private void process(Dictionary<string, List<ControllerInvocationInfo>> map, bool failOnNotMet)
  154. {
  155. foreach (string key in map.Keys)
  156. {
  157. List<ControllerInvocationInfo> prov;
  158. if (!providers.TryGetValue(key, out prov))
  159. {
  160. if (failOnNotMet)
  161. throw new InvalidOperationException("Requirement " + key + " was not met by any provider");
  162. }
  163. else
  164. // there can be multiple providers of the same parameter. all of them have to be
  165. // loaded into the list.
  166. foreach (ControllerInvocationInfo info in prov)
  167. // for each provider, we need to load in its list of dependent controllers
  168. foreach (ControllerInvocationInfo dep in map[key])
  169. getAndCreateList(info, finalDependencies).Add(dep);
  170. }
  171. }
  172. /// <summary>
  173. /// Loads the required/dependent/provider maps based on the list of controllers
  174. /// </summary>
  175. /// <param name="sortedList">The sorted list of controllers.</param>
  176. private void populateMaps(List<ControllerInvocationInfo> sortedList)
  177. {
  178. requirements.Clear();
  179. dependencies.Clear();
  180. providers.Clear();
  181. finalDependencies.Clear();
  182. foreach (ControllerInvocationInfo controller in sortedList)
  183. {
  184. ControllerDescriptor info = controller.BindPoint.Controller;
  185. foreach (string req in info.Requires)
  186. getAndCreateList(req, requirements).Add(controller);
  187. foreach (string dep in info.DependsOn)
  188. getAndCreateList(dep, dependencies).Add(controller);
  189. foreach (string prov in info.Provides)
  190. getAndCreateList(prov, providers).Add(controller);
  191. }
  192. }
  193. }
  194. }