IIdentifiableEntity.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. using System;
  2. using System.Text;
  3. using System.Collections.Generic;
  4. namespace Wayne.Lib
  5. {
  6. /// <summary>
  7. /// The IIdentifiableEntity represents an entity of some sort that has an integer ID and a possible parent.
  8. /// </summary>
  9. public interface IIdentifiableEntity
  10. {
  11. #region Properties
  12. /// <summary>
  13. /// The ID of the entity.
  14. /// </summary>
  15. int Id { get; }
  16. /// <summary>
  17. /// The main type of entity.
  18. /// </summary>
  19. string EntityType { get; }
  20. /// <summary>
  21. /// This is used by the logger and should never be set by inheriting classes
  22. /// </summary>
  23. string FullEntityName { get; set; }
  24. /// <summary>
  25. /// A more refined type of the entity, e.g. a specific implementation or brand.
  26. /// </summary>
  27. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubType")]
  28. string EntitySubType { get; }
  29. /// <summary>
  30. /// Reference to a possible parent device.
  31. /// </summary>
  32. IIdentifiableEntity ParentEntity { get; }
  33. #endregion
  34. }
  35. /// <summary>
  36. /// A class implementing the IIdentifiableEntity interface.
  37. /// Also contains static properties and methods that handles IIdentifiableEntity.
  38. /// </summary>
  39. public class IdentifiableEntity : IIdentifiableEntity
  40. {
  41. #region Fields
  42. private readonly int id;
  43. private readonly string entityType;
  44. private readonly string entitySubType;
  45. private readonly IIdentifiableEntity parentEntity;
  46. #endregion
  47. #region Construction
  48. static IdentifiableEntity()
  49. {
  50. Empty = new IdentifiableEntity(NoId, string.Empty, string.Empty, null);
  51. }
  52. /// <summary>
  53. /// Construction
  54. /// </summary>
  55. /// <param name="id">The ID of the entity.</param>
  56. /// <param name="entityType">The main type of entity.</param>
  57. /// <param name="entitySubType">A more refined type of the entity, e.g. a specific implementation or brand.</param>
  58. /// <param name="parentEntity">Reference to a possible parent device.</param>
  59. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubType")]
  60. public IdentifiableEntity(int id, string entityType, string entitySubType, IIdentifiableEntity parentEntity)
  61. {
  62. this.id = id;
  63. this.entityType = entityType;
  64. this.entitySubType = entitySubType;
  65. this.parentEntity = parentEntity;
  66. }
  67. #endregion
  68. #region IIdentifiableEntity Members
  69. /// <summary>
  70. /// The ID of the entity.
  71. /// </summary>
  72. public int Id
  73. {
  74. get { return id; }
  75. }
  76. /// <summary>
  77. /// The main type of entity.
  78. /// </summary>
  79. public string EntitySubType
  80. {
  81. get { return entitySubType; }
  82. }
  83. /// <summary>
  84. /// A more refined type of the entity, e.g. a specific implementation or brand.
  85. /// </summary>
  86. public string EntityType
  87. {
  88. get { return entityType; }
  89. }
  90. /// <summary>
  91. /// This is used by the logger and should never be set by inheriting classes
  92. /// </summary>
  93. public string FullEntityName { get; set; }
  94. /// <summary>
  95. /// Reference to a possible parent device.
  96. /// </summary>
  97. public IIdentifiableEntity ParentEntity
  98. {
  99. get { return parentEntity; }
  100. }
  101. #endregion
  102. #region Misc Static Members
  103. /// <summary>
  104. /// A value representing a non-Id.
  105. /// </summary>
  106. public const int NoId = int.MinValue;
  107. /// <summary>
  108. /// An empty IdentifiableEntity.
  109. /// </summary>
  110. public static IdentifiableEntity Empty { get; private set; }
  111. /// <summary>
  112. /// Gets an IIdentifiableEntity-array of the ancestors of the given entity.
  113. /// The first one in the list is the given entity itself, and the last one is the root-parent.
  114. /// </summary>
  115. /// <param name="entity"></param>
  116. /// <returns></returns>
  117. public static IIdentifiableEntity[] GetAncestorArray(IIdentifiableEntity entity)
  118. {
  119. List<IIdentifiableEntity> ancestors = new List<IIdentifiableEntity>();
  120. while (entity != null)
  121. {
  122. ancestors.Add(entity);
  123. entity = entity.ParentEntity;
  124. }
  125. return ancestors.ToArray();
  126. }
  127. /// <summary>
  128. /// Tests equality between two identifieable entities on the regards on the
  129. /// Id, EntityType, EntitySubtype, and the parent ancestry.
  130. /// </summary>
  131. /// <param name="entity1"></param>
  132. /// <param name="entity2"></param>
  133. /// <returns></returns>
  134. public static bool Equals(IIdentifiableEntity entity1, IIdentifiableEntity entity2)
  135. {
  136. if (entity1 == null)
  137. throw new ArgumentNullException("entity1");
  138. if (entity2 == null)
  139. throw new ArgumentNullException("entity2");
  140. if (entity1 == entity2)
  141. return true;
  142. EnsureFullEntityName(entity1);
  143. EnsureFullEntityName(entity2);
  144. return entity1.FullEntityName == entity2.FullEntityName;
  145. }
  146. private static void EnsureFullEntityName(IIdentifiableEntity entity1)
  147. {
  148. if (string.IsNullOrEmpty(entity1.FullEntityName))
  149. {
  150. entity1.FullEntityName = ToString(entity1, true);
  151. }
  152. }
  153. #endregion
  154. #region ToString
  155. /// <summary>
  156. /// Composes a string from this IIdentifiableEntity.
  157. /// </summary>
  158. /// <returns></returns>
  159. public override string ToString()
  160. {
  161. if (string.IsNullOrEmpty(FullEntityName))
  162. {
  163. FullEntityName = ToString(this, true);
  164. }
  165. return FullEntityName;
  166. }
  167. /// <summary>
  168. /// Composes a string from an IIdentifiableEntity.
  169. /// </summary>
  170. /// <param name="entity">The entity to convert to a string.</param>
  171. /// <returns></returns>
  172. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
  173. public static string ToString(IIdentifiableEntity entity)
  174. {
  175. return ToString(entity, false);
  176. }
  177. /// <summary>
  178. /// Composes a string from an IIdentifiableEntity, possibly with the ancestors included.
  179. /// </summary>
  180. /// <param name="entity">The entity to convert to a string.</param>
  181. /// <param name="ancestors">Should the ancestors be included?</param>
  182. /// <returns></returns>
  183. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
  184. public static string ToString(IIdentifiableEntity entity, bool ancestors)
  185. {
  186. if ((entity == null) || (entity == Empty))
  187. return string.Empty;
  188. StringBuilder builder = new StringBuilder();
  189. if (ancestors)
  190. {
  191. List<string> entities = new List<string>();
  192. do
  193. {
  194. // The EntityType
  195. builder.Append(entity.EntityType);
  196. // The EntitySubType
  197. if (!string.IsNullOrEmpty(entity.EntitySubType))
  198. {
  199. builder.Append("{");
  200. builder.Append(entity.EntitySubType);
  201. builder.Append("}");
  202. }
  203. // The Id
  204. if (entity.Id != NoId)
  205. {
  206. builder.Append('#');
  207. builder.Append(entity.Id.ToString(System.Globalization.CultureInfo.InvariantCulture));
  208. }
  209. entity = entity.ParentEntity;
  210. entities.Add(builder.ToString());
  211. builder.Length = 0;
  212. }
  213. while (entity != null);
  214. for (int i = entities.Count - 1; i >= 0; i--)
  215. {
  216. builder.Append("\\");
  217. builder.Append(entities[i]);
  218. }
  219. }
  220. else
  221. {
  222. // The EntityType
  223. builder.Append(entity.EntityType);
  224. // The EntitySubType
  225. if (!string.IsNullOrEmpty(entity.EntitySubType))
  226. {
  227. builder.Append("{");
  228. builder.Append(entity.EntitySubType);
  229. builder.Append("}");
  230. }
  231. // The Id
  232. if (entity.Id != NoId)
  233. builder.Append(entity.Id.ToString(System.Globalization.CultureInfo.InvariantCulture));
  234. }
  235. return builder.ToString();
  236. }
  237. #endregion
  238. #region Parse
  239. /// <summary>
  240. /// Parses an Ancestor string into a list of Identifiable entities, where the first item is the bottommost parent,
  241. /// </summary>
  242. /// <param name="ancestryString"></param>
  243. /// <returns></returns>
  244. public static IIdentifiableEntity[] ParseAncestorString(string ancestryString)
  245. {
  246. string[] entityStrings = ancestryString.Split('\\');
  247. List<IIdentifiableEntity> identifiableEntityList = new List<IIdentifiableEntity>();
  248. IIdentifiableEntity currentParent = null;
  249. System.Text.RegularExpressions.Regex allowedEntityStringFormat = new System.Text.RegularExpressions.Regex(@"^[a-zA-Z0-9_\.]+({[a-zA-Z0-9_\.]+})?(#[0-9]+)?$");
  250. foreach (string entityString in entityStrings)
  251. {
  252. if (string.IsNullOrEmpty(entityString))
  253. continue;
  254. if (!allowedEntityStringFormat.IsMatch(entityString))
  255. return new IIdentifiableEntity[0];
  256. string entityType;
  257. string entitySubType = string.Empty;
  258. int id = NoId;
  259. //Entity string can have the following formats:
  260. // 1. Entity
  261. // 2. Entity{SubType}
  262. // 3. Entity{SubType}#Id
  263. // 4. Entity#Id
  264. string[] firstSplit = entityString.Split('#');
  265. if (firstSplit.Length >= 1)
  266. {
  267. //Is it case 1 or 2?
  268. List<string> secondSplit = new List<string>(firstSplit[0].Split('{', '}'));
  269. while (secondSplit.Contains(string.Empty))
  270. secondSplit.Remove(string.Empty);
  271. if (secondSplit.Count == 1)
  272. {
  273. //Was Case 1
  274. entityType = secondSplit[0];
  275. }
  276. else if (secondSplit.Count == 2)
  277. {
  278. //Was Case 2
  279. entityType = secondSplit[0];
  280. entitySubType = secondSplit[1];
  281. }
  282. else
  283. return new IIdentifiableEntity[0];
  284. if (firstSplit.Length == 2)
  285. id = int.Parse(firstSplit[1], System.Globalization.CultureInfo.InvariantCulture);
  286. else if (firstSplit.Length > 2)
  287. return new IIdentifiableEntity[0];
  288. }
  289. else
  290. return new IIdentifiableEntity[0];
  291. IIdentifiableEntity entity = new IdentifiableEntity(id, entityType, entitySubType, currentParent);
  292. identifiableEntityList.Add(entity);
  293. currentParent = entity;
  294. }
  295. return identifiableEntityList.ToArray();
  296. }
  297. /// <summary>
  298. /// Parses an ancestry string entity into an IdentifiableEntity object whith newly created Identifiable entities for parents.
  299. /// </summary>
  300. /// <param name="path"></param>
  301. /// <returns></returns>
  302. public static IIdentifiableEntity Parse(string path)
  303. {
  304. System.Text.RegularExpressions.Regex allowedEntityStringFormat = new System.Text.RegularExpressions.Regex(@"^[a-zA-Z0-9_\.]+({[a-zA-Z0-9_\.]+})?(#[0-9]+)?$");
  305. string[] entityStrings = path.TrimStart('\\').Split('\\');
  306. IdentifiableEntity currentParent = null;
  307. foreach (string entityString in entityStrings)
  308. {
  309. if (!allowedEntityStringFormat.IsMatch(entityString))
  310. return null;
  311. string entityType;
  312. string entitySubType = string.Empty;
  313. int id = NoId;
  314. //Entity string can have the following formats:
  315. // 1. Entity
  316. // 2. Entity{SubType}
  317. // 3. Entity{SubType}#Id
  318. // 4. Entity#Id
  319. string[] firstSplit = entityString.Split('#');
  320. if (firstSplit.Length >= 1)
  321. {
  322. //Is it case 1 or 2?
  323. List<string> secondSplit = new List<string>(firstSplit[0].Split('{', '}'));
  324. while (secondSplit.Contains(string.Empty))
  325. secondSplit.Remove(string.Empty);
  326. if (secondSplit.Count == 1)
  327. {
  328. //Was Case 1
  329. entityType = secondSplit[0];
  330. }
  331. else if (secondSplit.Count == 2)
  332. {
  333. //Was Case 2
  334. entityType = secondSplit[0];
  335. entitySubType = secondSplit[1];
  336. }
  337. else
  338. return null;
  339. if (firstSplit.Length == 2)
  340. id = int.Parse(firstSplit[1], System.Globalization.CultureInfo.InvariantCulture);
  341. else if (firstSplit.Length > 2)
  342. return null;
  343. }
  344. else
  345. return null;
  346. IdentifiableEntity entity = new IdentifiableEntity(id, entityType, entitySubType, currentParent);
  347. currentParent = entity;
  348. }
  349. return currentParent;
  350. }
  351. public class EqualityComparer : IEqualityComparer<IIdentifiableEntity>
  352. {
  353. public bool Equals(IIdentifiableEntity x, IIdentifiableEntity y)
  354. {
  355. return IdentifiableEntity.Equals(x, y);
  356. }
  357. public int GetHashCode(IIdentifiableEntity obj)
  358. {
  359. EnsureFullEntityName(obj);
  360. return obj.FullEntityName.GetHashCode();
  361. }
  362. }
  363. #endregion
  364. }
  365. }