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

/src/FluentNHibernate/Visitors/RelationshipPairingVisitor.cs

http://github.com/jagregory/fluent-nhibernate
C# | 238 lines | 177 code | 46 blank | 15 comment | 38 complexity | dcc34bab2c48a3efb861f8c1f6feae45 MD5 | raw file
Possible License(s): BSD-3-Clause, CC-BY-SA-3.0, Apache-2.0
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using FluentNHibernate.MappingModel;
  4. using FluentNHibernate.MappingModel.Collections;
  5. using FluentNHibernate.Utils;
  6. namespace FluentNHibernate.Visitors
  7. {
  8. public delegate void PairBiDirectionalManyToManySidesDelegate(CollectionMapping current, IEnumerable<CollectionMapping> possibles, bool wasResolved);
  9. public class RelationshipPairingVisitor : DefaultMappingModelVisitor
  10. {
  11. readonly PairBiDirectionalManyToManySidesDelegate userControlledPair;
  12. readonly List<CollectionMapping> manyToManys = new List<CollectionMapping>();
  13. readonly List<CollectionMapping> oneToManys = new List<CollectionMapping>();
  14. readonly List<ManyToOneMapping> references = new List<ManyToOneMapping>();
  15. public RelationshipPairingVisitor(PairBiDirectionalManyToManySidesDelegate userControlledPair)
  16. {
  17. this.userControlledPair = userControlledPair;
  18. }
  19. public override void Visit(IEnumerable<HibernateMapping> mappings)
  20. {
  21. base.Visit(mappings);
  22. PairManyToManys(manyToManys);
  23. PairOneToManys(oneToManys, references);
  24. }
  25. public override void ProcessCollection(CollectionMapping mapping)
  26. {
  27. if (mapping.Relationship is ManyToManyMapping)
  28. manyToManys.Add(mapping);
  29. if (mapping.Relationship is OneToManyMapping)
  30. oneToManys.Add(mapping);
  31. }
  32. public override void ProcessManyToOne(ManyToOneMapping manyToOneMapping)
  33. {
  34. references.Add(manyToOneMapping);
  35. }
  36. static void PairOneToManys(IEnumerable<CollectionMapping> collections, IEnumerable<ManyToOneMapping> refs)
  37. {
  38. var orderedCollections = collections.OrderBy(x => x.Name).ToArray();
  39. var orderedRefs = refs.OrderBy(x => x.Name).ToArray();
  40. foreach (var collection in orderedCollections)
  41. {
  42. var type = collection.ContainingEntityType;
  43. var childType = collection.ChildType;
  44. var reference = orderedRefs
  45. .FirstOrDefault(x =>
  46. x.OtherSide == null &&
  47. x.Class.GetUnderlyingSystemType() == type &&
  48. x.ContainingEntityType == childType);
  49. if (reference == null) continue;
  50. collection.OtherSide = reference;
  51. reference.OtherSide = collection;
  52. }
  53. }
  54. void PairManyToManys(IEnumerable<CollectionMapping> rs)
  55. {
  56. if (!rs.Any()) return;
  57. var current = rs.First();
  58. var wasResolved = true;
  59. // get any candidates for the other side of the relationship
  60. var candidates = rs
  61. .Where(x => both_collections_point_to_each_others_types(x, current));
  62. // try to pair the current side with the potential other sides
  63. var mapping = current;
  64. var candidatesCount = candidates.Count();
  65. if (candidatesCount == 1)
  66. mapping = PairExactMatches(rs, current, candidates);
  67. else if (candidatesCount > 1)
  68. mapping = PairFuzzyMatches(rs, current, candidates);
  69. if (mapping == null)
  70. {
  71. // couldn't pair at all, going to defer to the user for this one
  72. // and if they can't do anything we'll just treat it as uni-directional
  73. mapping = current;
  74. wasResolved = false;
  75. }
  76. userControlledPair(mapping, rs, wasResolved);
  77. // both collections have been paired, so remove them
  78. // from the available collections
  79. PairManyToManys(rs.Except(mapping, (CollectionMapping)mapping.OtherSide));
  80. }
  81. static string GetMemberName(Member member)
  82. {
  83. if (member is MethodMember && member.Name.StartsWith("Get"))
  84. return member.Name.Substring(3);
  85. return member.Name;
  86. }
  87. static LikenessContainer GetLikeness(CollectionMapping currentMapping, CollectionMapping mapping)
  88. {
  89. var currentMemberName = GetMemberName(currentMapping.Member);
  90. var mappingMemberName = GetMemberName(mapping.Member);
  91. return new LikenessContainer
  92. {
  93. Collection = currentMapping,
  94. CurrentMemberName = currentMemberName,
  95. MappingMemberName = mappingMemberName,
  96. Differences = StringLikeness.EditDistance(currentMemberName, mappingMemberName)
  97. };
  98. }
  99. static bool both_collections_point_to_each_others_types(CollectionMapping left, CollectionMapping right)
  100. {
  101. return left.ContainingEntityType == right.ChildType &&
  102. left.ChildType == right.ContainingEntityType;
  103. }
  104. static bool self_referenced_relation_does_not_point_to_itself(CollectionMapping left, CollectionMapping right)
  105. {
  106. return left.ChildType == left.ContainingEntityType && right.ChildType == right.ContainingEntityType && left != right;
  107. }
  108. static bool likeness_within_threshold(LikenessContainer current)
  109. {
  110. return current.Differences != current.CurrentMemberName.Length;
  111. }
  112. static CollectionMapping PairFuzzyMatches(IEnumerable<CollectionMapping> rs, CollectionMapping current, IEnumerable<CollectionMapping> potentialOtherSides)
  113. {
  114. // no exact matches found, drop down to a levenshtein distance
  115. var mapping = current;
  116. var likenesses = potentialOtherSides
  117. .Select(x => GetLikeness(x, current))
  118. .Where(likeness_within_threshold)
  119. .OrderBy(x => x.Differences);
  120. var first = likenesses.FirstOrDefault();
  121. if (first == null || AnyHaveSameLikeness(likenesses, first))
  122. {
  123. // couldn't find a definitive match, return nothing and we'll handle
  124. // this further up
  125. return null;
  126. }
  127. var otherSide = likenesses.First().Collection;
  128. // got the other side of the relationship
  129. // lets make sure that the side that we're on now (mapping!)
  130. // is actually the relationship we want
  131. mapping = FindAlternative(rs, mapping, otherSide) ?? mapping;
  132. mapping.OtherSide = otherSide;
  133. otherSide.OtherSide = mapping;
  134. return mapping;
  135. }
  136. static CollectionMapping PairExactMatches(IEnumerable<CollectionMapping> rs, CollectionMapping current, IEnumerable<CollectionMapping> potentialOtherSides)
  137. {
  138. var otherSide = potentialOtherSides.Single();
  139. // got the other side of the relationship
  140. // lets make sure that the side that we're on now (mapping!)
  141. // is actually the relationship we want
  142. var mapping = FindAlternative(rs, current, otherSide) ?? current;
  143. mapping.OtherSide = otherSide;
  144. otherSide.OtherSide = mapping;
  145. return mapping;
  146. }
  147. static CollectionMapping FindAlternative(IEnumerable<CollectionMapping> rs, CollectionMapping current, CollectionMapping otherSide)
  148. {
  149. var alternative = rs
  150. .Where(x => self_referenced_relation_does_not_point_to_itself(x, current))
  151. .Where(x => x.ContainingEntityType == current.ContainingEntityType &&
  152. x.ChildType == current.ChildType)
  153. .Select(x => GetLikeness(x, otherSide))
  154. .OrderBy(x => x.Differences)
  155. .FirstOrDefault();
  156. if (alternative == null)
  157. return null;
  158. return alternative.Collection;
  159. }
  160. static bool AnyHaveSameLikeness(IEnumerable<LikenessContainer> likenesses, LikenessContainer current)
  161. {
  162. return likenesses
  163. .Except(current)
  164. .Any(x => x.Differences == current.Differences);
  165. }
  166. class LikenessContainer
  167. {
  168. public CollectionMapping Collection { get; set; }
  169. public string CurrentMemberName { get; set; }
  170. public string MappingMemberName { get; set; }
  171. public int Differences { get; set; }
  172. public override bool Equals(object obj)
  173. {
  174. if (obj is LikenessContainer)
  175. {
  176. return ((LikenessContainer)obj).CurrentMemberName == CurrentMemberName &&
  177. ((LikenessContainer)obj).MappingMemberName == MappingMemberName;
  178. }
  179. return false;
  180. }
  181. public override int GetHashCode()
  182. {
  183. unchecked
  184. {
  185. int result = (CurrentMemberName != null ? CurrentMemberName.GetHashCode() : 0);
  186. result = (result * 397) ^ (MappingMemberName != null ? MappingMemberName.GetHashCode() : 0);
  187. return result;
  188. }
  189. }
  190. }
  191. }
  192. }