PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/source/library/Interlace.UserInterface/Utilities/TreeManager.cs

https://bitbucket.org/VahidN/interlace
C# | 233 lines | 121 code | 37 blank | 75 comment | 12 complexity | e7e45ebbcdcb36752cd070392ecab1bc MD5 | raw file
  1. #region Using Directives and Copyright Notice
  2. // Copyright (c) 2007-2010, Computer Consultancy Pty Ltd
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. // * Redistributions of source code must retain the above copyright
  8. // notice, this list of conditions and the following disclaimer.
  9. // * Redistributions in binary form must reproduce the above copyright
  10. // notice, this list of conditions and the following disclaimer in the
  11. // documentation and/or other materials provided with the distribution.
  12. // * Neither the name of the Computer Consultancy Pty Ltd nor the
  13. // names of its contributors may be used to endorse or promote products
  14. // derived from this software without specific prior written permission.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. // ARE DISCLAIMED. IN NO EVENT SHALL COMPUTER CONSULTANCY PTY LTD BE LIABLE
  20. // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  21. // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  22. // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  23. // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  24. // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  25. // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  26. // DAMAGE.
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Text;
  30. using SD.LLBLGen.Pro.ORMSupportClasses;
  31. #endregion
  32. namespace Interlace.Utilities
  33. {
  34. public abstract class TreeManager<TEntity, TCollection>
  35. where TEntity : EntityBase2
  36. where TCollection : IEntityCollection2
  37. {
  38. protected abstract EntityField2 IdField { get; }
  39. protected abstract EntityField2 ParentIdField { get; }
  40. protected abstract EntityField2 NestedLeftField { get; }
  41. protected abstract EntityField2 NestedRightField { get; }
  42. protected abstract TCollection CreateEntityCollection();
  43. protected abstract TCollection GetChildrenCollection(TEntity entity);
  44. /// <summary>
  45. /// Checks the database for root entity, which would indicate that the
  46. /// database has been initialised.
  47. /// </summary>
  48. /// <returns><c>true</c> if the root entity exists; otherwise, <c>false</c>.</returns>
  49. public bool RootEntityExists(IDataAccessAdapter adapter)
  50. {
  51. TCollection rootEntity = CreateEntityCollection();
  52. RelationPredicateBucket bucket = new RelationPredicateBucket(
  53. new PredicateExpression(ParentIdField == DBNull.Value));
  54. adapter.FetchEntityCollection(rootEntity, bucket, 2);
  55. if (rootEntity.Count > 1)
  56. {
  57. throw new InvalidOperationException("More than one root entity was found " +
  58. "in a database table using trees.");
  59. }
  60. return rootEntity.Count == 1;
  61. }
  62. /// <summary>
  63. /// Checks the database to test if the table is empty.
  64. /// </summary>
  65. /// <returns><c>true</c> if any entity exists; otherwise, <c>false</c>.</returns>
  66. public bool AnyEntityExists(IDataAccessAdapter adapter)
  67. {
  68. TCollection anyEntity = CreateEntityCollection();
  69. adapter.FetchEntityCollection(anyEntity, null, 1);
  70. return anyEntity.Count > 0;
  71. }
  72. /// <summary>
  73. /// Fixes the NestedLeft and NestedRight properties of a tree
  74. /// once it has been modified.
  75. /// </summary>
  76. /// <param name="rootAccount">The root entity of the tree.</param>
  77. /// <remarks>
  78. /// <para>A subtree can not be passed in to this
  79. /// function; it assumes 0 as the starting NestedLeft, and it
  80. /// could not allocate new numbers if it was passed a subtree.</para>
  81. /// <para>Calling this function on a tree of entities always
  82. /// results in all entities being marked dirty.
  83. /// </para>
  84. /// </remarks>
  85. public void RebuildNesting(TEntity rootEntity)
  86. {
  87. // To rebuild the nesting, we do a depth first search on the entity tree. The
  88. // left nesting value is set on entry, the right on exit. Each time
  89. // a nesting value is set the next one is incremented.
  90. if (rootEntity.GetCurrentFieldValue(ParentIdField.FieldIndex) != null)
  91. {
  92. throw new ArgumentException("A non-root entity was passed; only the root " +
  93. "entity can be passed to rebuild a tree", "rootEntity");
  94. }
  95. int clock = 0;
  96. RebuildNestingRecursor(rootEntity, ref clock);
  97. }
  98. void RebuildNestingRecursor(TEntity entity, ref int clock)
  99. {
  100. entity.SetNewFieldValue(NestedLeftField.FieldIndex, clock++);
  101. foreach (TEntity child in GetChildrenCollection(entity))
  102. {
  103. RebuildNestingRecursor(child, ref clock);
  104. }
  105. entity.SetNewFieldValue(NestedRightField.FieldIndex, clock++);
  106. }
  107. /// <summary>
  108. /// Fetches all elements in to a tree.
  109. /// </summary>
  110. /// <returns>The root node of the tree, with each nodes
  111. /// Children collections populated with child elements.</returns>
  112. public TEntity FetchTree(IDataAccessAdapter adapter)
  113. {
  114. TCollection entities = CreateEntityCollection();
  115. adapter.FetchEntityCollection(entities, null);
  116. // Build a hash of all entities by key:
  117. Dictionary<object, TEntity> entitiesMap = new Dictionary<object, TEntity>();
  118. foreach (TEntity entity in entities)
  119. {
  120. entitiesMap[entity.GetCurrentFieldValue(IdField.FieldIndex)] = entity;
  121. }
  122. // Add child entities to the parent collection:
  123. TEntity rootEntity = null;
  124. foreach (TEntity entity in entities)
  125. {
  126. object parentId = entity.GetCurrentFieldValue(ParentIdField.FieldIndex);
  127. if (parentId == null)
  128. {
  129. if (rootEntity != null)
  130. {
  131. throw new InvalidOperationException(string.Format(
  132. "Multiple root entities found while building entity tree; " +
  133. "\"{0}\" and \"{1}\" are the keys of the first two found.",
  134. parentId, entity.GetCurrentFieldValue(IdField.FieldIndex)));
  135. }
  136. rootEntity = entity;
  137. }
  138. else
  139. {
  140. if (!entitiesMap.ContainsKey(parentId))
  141. {
  142. throw new InvalidOperationException(string.Format(
  143. "The entity with the key \"{0}\" refers to a non-existant " +
  144. "parent element with the key \"{1}\".",
  145. entity.GetCurrentFieldValue(IdField.FieldIndex), parentId));
  146. }
  147. GetChildrenCollection(entitiesMap[parentId]).Add(entity);
  148. }
  149. }
  150. if (rootEntity == null)
  151. {
  152. throw new InvalidOperationException(
  153. "No root entity was found in the queried tree table.");
  154. }
  155. return rootEntity;
  156. }
  157. /// <summary>
  158. /// Checks the database to find if an entity has any child fields.
  159. /// </summary>
  160. /// <param name="entity">The entity.</param>
  161. /// <returns><c>true</c> if the entity has children; otherwise, <c>false</c>.</returns>
  162. public bool EntityHasChildren(IDataAccessAdapter adapter, TEntity entity)
  163. {
  164. TCollection childEntities = CreateEntityCollection();
  165. RelationPredicateBucket bucket = new RelationPredicateBucket();
  166. bucket.PredicateExpression.Add(GetDescendantsPredicate(entity));
  167. adapter.FetchEntityCollection(childEntities, bucket, 1);
  168. return childEntities.Count > 0;
  169. }
  170. /// <summary>
  171. /// Gets a predicate that would generate the set of all descendant nodes to
  172. /// the given node.
  173. /// </summary>
  174. /// <remarks>The specified node is not included in the generated set.
  175. /// <param name="entity">The entity.</param>
  176. /// <returns></returns>
  177. public IPredicateExpression GetDescendantsPredicate(TEntity entity)
  178. {
  179. return
  180. NestedLeftField > entity.GetCurrentFieldValue(NestedLeftField.FieldIndex) &
  181. NestedRightField < entity.GetCurrentFieldValue(NestedRightField.FieldIndex);
  182. }
  183. /// <summary>
  184. /// Gets a predicate that would generate the set of all descendant nodes
  185. /// plus the specified node.
  186. /// </summary>
  187. /// <param name="entity">The entity.</param>
  188. /// <returns></returns>
  189. public IPredicateExpression GetDescendantsAndParentPredicate(TEntity entity)
  190. {
  191. return
  192. NestedLeftField >= entity.GetCurrentFieldValue(NestedLeftField.FieldIndex) &
  193. NestedRightField <= entity.GetCurrentFieldValue(NestedRightField.FieldIndex);
  194. }
  195. }
  196. }