BaseObject.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. using AutoProperties;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Concurrent;
  5. using System.Collections.Generic;
  6. using System.ComponentModel;
  7. using System.Linq;
  8. using System.Linq.Expressions;
  9. using System.Reflection;
  10. using System.Runtime.CompilerServices;
  11. using System.Text.Json.Serialization;
  12. namespace InABox.Core
  13. {
  14. public class DoNotSerialize : Attribute
  15. {
  16. }
  17. public interface IBaseObject
  18. {
  19. public bool IsChanged();
  20. public void CancelChanges();
  21. public void CommitChanges();
  22. public bool IsObserving();
  23. public void SetObserving(bool active);
  24. }
  25. public interface IOriginalValues : IEnumerable<KeyValuePair<string, object?>>
  26. {
  27. public object? this[string key] { get; set; }
  28. public bool ContainsKey(string key);
  29. public void Clear();
  30. public bool TryGetValue(string key, out object? value);
  31. public bool TryAdd(string key, object? value);
  32. public void Remove(string key);
  33. public object? GetValueOrDefault(string key)
  34. {
  35. if(TryGetValue(key, out object? value)) return value;
  36. return null;
  37. }
  38. }
  39. public class OriginalValues : IOriginalValues
  40. {
  41. public OriginalValues()
  42. {
  43. }
  44. public ConcurrentDictionary<string, object?> Dictionary { get; set; } = new ConcurrentDictionary<string, object?>();
  45. public object? this[string key] { get => Dictionary[key]; set => Dictionary[key] = value; }
  46. public void Clear()
  47. {
  48. Dictionary.Clear();
  49. }
  50. public bool ContainsKey(string key) => Dictionary.ContainsKey(key);
  51. public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
  52. {
  53. return Dictionary.GetEnumerator();
  54. }
  55. public bool TryGetValue(string key, out object? value)
  56. {
  57. return Dictionary.TryGetValue(key, out value);
  58. }
  59. public bool TryAdd(string key, object? value)
  60. {
  61. return Dictionary.TryAdd(key, value);
  62. }
  63. public void Remove(string key)
  64. {
  65. (Dictionary as IDictionary<string, object?>).Remove(key);
  66. }
  67. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  68. }
  69. public interface ILoadedColumns : IEnumerable<string>
  70. {
  71. public bool Add(string key);
  72. public bool Contains(string key);
  73. }
  74. public class LoadedColumns : ILoadedColumns
  75. {
  76. private HashSet<string> Columns = new HashSet<string>();
  77. public bool Add(string key)
  78. {
  79. return Columns.Add(key);
  80. }
  81. public bool Contains(string key)
  82. {
  83. return Columns.Contains(key);
  84. }
  85. public IEnumerator<string> GetEnumerator()
  86. {
  87. return Columns.GetEnumerator();
  88. }
  89. IEnumerator IEnumerable.GetEnumerator()
  90. {
  91. return Columns.GetEnumerator();
  92. }
  93. }
  94. /// <summary>
  95. /// Observable object with INotifyPropertyChanged implemented
  96. /// </summary>
  97. public abstract class BaseObject : INotifyPropertyChanged, IBaseObject, IJsonOnDeserializing, IJsonOnDeserialized
  98. {
  99. public BaseObject()
  100. {
  101. SetObserving(false);
  102. Init();
  103. SetObserving(true);
  104. }
  105. internal bool _disabledInterceptor;
  106. protected T InitializeField<T>(ref T? field, [CallerMemberName] string name = "")
  107. where T : BaseObject, ISubObject, new()
  108. {
  109. if(field is null)
  110. {
  111. var value = new T();
  112. value.SetLinkedParent(this);
  113. value.SetLinkedPath(name);
  114. value.SetObserving(_observing);
  115. field = value;
  116. }
  117. return field;
  118. }
  119. [GetInterceptor]
  120. protected T GetValue<T>(Type propertyType, ref T field, string name)
  121. {
  122. if (_disabledInterceptor) return field;
  123. if(field is null && propertyType.HasInterface<ISubObject>() && !propertyType.IsAbstract)
  124. {
  125. var value = Activator.CreateInstance<T>();
  126. var subObj = (value as ISubObject)!;
  127. subObj.SetLinkedParent(this);
  128. subObj.SetLinkedPath(name);
  129. if(subObj is BaseObject obj)
  130. {
  131. obj.SetObserving(_observing);
  132. }
  133. field = value;
  134. }
  135. return field;
  136. }
  137. [SetInterceptor]
  138. protected void SetValue<T>(ref T field, T newValue)
  139. {
  140. field = newValue;
  141. }
  142. public void OnDeserializing()
  143. {
  144. if (_observing)
  145. SetObserving(false);
  146. }
  147. public void OnDeserialized()
  148. {
  149. if (!_observing)
  150. SetObserving(true);
  151. }
  152. protected virtual void Init()
  153. {
  154. LoadedColumns = CreateLoadedColumns();
  155. CheckSequence();
  156. }
  157. private void CheckSequence()
  158. {
  159. if (this is ISequenceable seq && seq.Sequence <= 0)
  160. {
  161. seq.Sequence = CoreUtils.GenerateSequence();
  162. }
  163. }
  164. #region Observing Flags
  165. public static bool GlobalObserving = true;
  166. private bool _observing = false;
  167. public bool IsObserving()
  168. {
  169. return GlobalObserving && _observing;
  170. }
  171. public void SetObserving(bool active)
  172. {
  173. bApplyingChanges = true;
  174. _observing = active;
  175. _disabledInterceptor = true;
  176. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  177. oo.SetObserving(active);
  178. _disabledInterceptor = false;
  179. bApplyingChanges = false;
  180. }
  181. protected virtual void DoPropertyChanged(string name, object? before, object? after)
  182. {
  183. }
  184. public event PropertyChangedEventHandler? PropertyChanged;
  185. private bool bApplyingChanges;
  186. private bool bChanged;
  187. private IOriginalValues? _originalValues;
  188. [DoNotPersist]
  189. [DoNotSerialize]
  190. public IOriginalValues OriginalValueList
  191. {
  192. get
  193. {
  194. _originalValues ??= CreateOriginalValues();
  195. return _originalValues;
  196. }
  197. }
  198. [DoNotPersist]
  199. public ConcurrentDictionary<string, object?>? OriginalValues
  200. {
  201. get
  202. {
  203. if(OriginalValueList is OriginalValues v)
  204. {
  205. return v.Dictionary;
  206. }
  207. else
  208. {
  209. return null;
  210. }
  211. }
  212. set
  213. {
  214. if(value != null && OriginalValueList is OriginalValues v)
  215. {
  216. v.Dictionary = value;
  217. }
  218. }
  219. }
  220. [DoNotPersist]
  221. [DoNotSerialize]
  222. [JsonIgnore]
  223. public ILoadedColumns LoadedColumns { get; set; }
  224. protected virtual void SetChanged(string name, object? before, object? after)
  225. {
  226. bChanged = true;
  227. if (!bApplyingChanges)
  228. {
  229. try
  230. {
  231. bApplyingChanges = true;
  232. DoPropertyChanged(name, before, after);
  233. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  234. }
  235. catch (Exception)
  236. {
  237. }
  238. bApplyingChanges = false;
  239. }
  240. }
  241. // This function is *only* meant to be called by EnclosedEntity and EntityLink
  242. internal void CascadePropertyChanged(string name, object? before, object? after)
  243. {
  244. SetChanged(name, before, after);
  245. }
  246. private bool QueryChanged()
  247. {
  248. if (OriginalValueList.Any())
  249. return true;
  250. _disabledInterceptor = true;
  251. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  252. if (oo.IsChanged())
  253. {
  254. _disabledInterceptor = false;
  255. return true;
  256. }
  257. _disabledInterceptor = false;
  258. return false;
  259. }
  260. public void OnPropertyChanged(string name, object? before, object? after)
  261. {
  262. if (!IsObserving())
  263. return;
  264. if (name.Equals("IsChanged") || name.Equals("Observing") || name.Equals("OriginalValues"))
  265. return;
  266. LoadedColumns.Add(name);
  267. if (!BaseObjectExtensions.HasChanged(before, after))
  268. return;
  269. if (!OriginalValueList.ContainsKey(name))
  270. OriginalValueList[name] = before;
  271. SetChanged(name, before, after);
  272. }
  273. protected virtual IOriginalValues CreateOriginalValues()
  274. {
  275. return new OriginalValues();
  276. }
  277. protected virtual ILoadedColumns CreateLoadedColumns()
  278. {
  279. return new LoadedColumns();
  280. }
  281. public bool IsChanged()
  282. {
  283. return IsObserving() ? QueryChanged() : bChanged;
  284. }
  285. public void CancelChanges()
  286. {
  287. bApplyingChanges = true;
  288. var bObs = IsObserving();
  289. SetObserving(false);
  290. foreach (var (key, value) in OriginalValueList)
  291. {
  292. try
  293. {
  294. var prop = DatabaseSchema.Property(GetType(), key);
  295. if(prop != null)
  296. {
  297. prop.Setter()(this, value);
  298. }
  299. else
  300. {
  301. Logger.Send(LogType.Error, "", $"'{key}' is not a property of {GetType().Name}");
  302. }
  303. }
  304. catch (Exception e)
  305. {
  306. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  307. }
  308. }
  309. OriginalValueList.Clear();
  310. bChanged = false;
  311. _disabledInterceptor = true;
  312. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  313. {
  314. oo.CancelChanges();
  315. }
  316. _disabledInterceptor = false;
  317. SetObserving(bObs);
  318. bApplyingChanges = false;
  319. }
  320. public void CommitChanges()
  321. {
  322. bApplyingChanges = true;
  323. OriginalValueList.Clear();
  324. bChanged = false;
  325. _disabledInterceptor = true;
  326. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  327. oo.CommitChanges();
  328. _disabledInterceptor = false;
  329. bApplyingChanges = false;
  330. }
  331. public string ChangedValues()
  332. {
  333. var result = new List<string>();
  334. var type = GetType();
  335. try
  336. {
  337. foreach (var (key, _) in OriginalValueList)
  338. try
  339. {
  340. if (UserProperties.ContainsKey(key))
  341. {
  342. var obj = UserProperties[key];
  343. result.Add(string.Format("[{0} = {1}]", key, obj != null ? obj.ToString() : "null"));
  344. }
  345. else
  346. {
  347. var prop = DatabaseSchema.Property(type, key);// GetType().GetProperty(key);
  348. if (prop is StandardProperty standard && standard.Loggable != null)
  349. {
  350. /*var attribute = //prop.GetCustomAttributes(typeof(LoggablePropertyAttribute), true).FirstOrDefault();
  351. if (attribute != null)
  352. {*/
  353. //var lpa = (LoggablePropertyAttribute)attribute;
  354. var format = standard.Loggable.Format;
  355. var value = standard.Getter()(this);
  356. if (string.IsNullOrEmpty(format))
  357. result.Add($"[{key} = {value}]");
  358. else
  359. result.Add(string.Format("[{0} = {1:" + format + "}]", key, value));
  360. //}
  361. }
  362. }
  363. }
  364. catch (Exception e)
  365. {
  366. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  367. }
  368. }
  369. catch (Exception e)
  370. {
  371. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  372. }
  373. return string.Join(" ", result);
  374. }
  375. #endregion
  376. #region UserProperties
  377. private UserProperties? _userproperties;
  378. private static readonly Dictionary<Type, Dictionary<string, object?>> DefaultProperties = new Dictionary<Type, Dictionary<string, object?>>();
  379. [DoNotPersist]
  380. public UserProperties UserProperties
  381. {
  382. get
  383. {
  384. if (_userproperties == null)
  385. {
  386. _userproperties = new UserProperties();
  387. var type = GetType();
  388. if (!DefaultProperties.TryGetValue(type, out var defaultProps))
  389. {
  390. defaultProps = new Dictionary<string, object?>();
  391. var props = DatabaseSchema.Properties(type).Where(x => x is CustomProperty);
  392. foreach (var field in props)
  393. defaultProps[field.Name] = DatabaseSchema.DefaultValue(field.PropertyType);
  394. DefaultProperties[type] = defaultProps;
  395. }
  396. _userproperties.LoadFromDictionary(defaultProps);
  397. _userproperties.OnPropertyChanged += (o, n, b, a) =>
  398. {
  399. if (IsObserving())
  400. OnPropertyChanged(n, b, a);
  401. };
  402. }
  403. return _userproperties;
  404. }
  405. }
  406. #endregion
  407. }
  408. public class BaseObjectSnapshot<T>
  409. where T : BaseObject
  410. {
  411. private readonly List<(IProperty, object?)> Values;
  412. private readonly T Object;
  413. public BaseObjectSnapshot(T obj)
  414. {
  415. Values = new List<(IProperty, object?)>();
  416. foreach(var property in DatabaseSchema.Properties(obj.GetType()))
  417. {
  418. Values.Add((property, property.Getter()(obj)));
  419. }
  420. Object = obj;
  421. }
  422. public void ResetObject()
  423. {
  424. Object.CancelChanges();
  425. var bObs = Object.IsObserving();
  426. Object.SetObserving(false);
  427. foreach(var (prop, value) in Values)
  428. {
  429. var oldValue = prop.Getter()(Object);
  430. prop.Setter()(Object, value);
  431. if(BaseObjectExtensions.HasChanged(oldValue, value))
  432. {
  433. Object.OriginalValueList[prop.Name] = oldValue;
  434. }
  435. }
  436. Object.SetObserving(bObs);
  437. }
  438. }
  439. public static class BaseObjectExtensions
  440. {
  441. public static T Clone<T>(this T obj) where T : BaseObject, new()
  442. {
  443. var newObj = new T();
  444. obj._disabledInterceptor = true;
  445. foreach(var property in DatabaseSchema.Properties(obj.GetType()))
  446. {
  447. if (property.Parent != null && property.Parent.NullSafeGetter()(obj) is null) continue;
  448. property.Setter()(newObj, property.Getter()(obj));
  449. }
  450. obj._disabledInterceptor = false;
  451. return newObj;
  452. }
  453. public static BaseObject Clone(BaseObject obj)
  454. {
  455. var newObj = (Activator.CreateInstance(obj.GetType()) as BaseObject)!;
  456. obj._disabledInterceptor = true;
  457. foreach(var property in DatabaseSchema.Properties(obj.GetType()))
  458. {
  459. if (property.Parent != null && property.Parent.NullSafeGetter()(obj) is null) continue;
  460. property.Setter()(newObj, property.Getter()(obj));
  461. }
  462. obj._disabledInterceptor = false;
  463. return newObj;
  464. }
  465. public static bool HasChanged(object? before, object? after)
  466. {
  467. if ((before == null || before.Equals("")) && (after == null || after.Equals("")))
  468. return false;
  469. if (before == null != (after == null))
  470. return true;
  471. if (!before!.GetType().Equals(after!.GetType()))
  472. return true;
  473. if (before is string[] && after is string[])
  474. return !(before as string[]).SequenceEqual(after as string[]);
  475. return !before.Equals(after);
  476. }
  477. public static bool HasColumn<T>(this T sender, string column)
  478. where T : BaseObject
  479. {
  480. return sender.LoadedColumns.Contains(column);
  481. }
  482. public static bool HasColumn<T, TType>(this T sender, Expression<Func<T, TType>> column)
  483. where T : BaseObject
  484. {
  485. return sender.LoadedColumns.Contains(CoreUtils.GetFullPropertyName(column, "."));
  486. }
  487. public static bool HasOriginalValue<T>(this T sender, string propertyname) where T : BaseObject
  488. {
  489. return sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(propertyname);
  490. }
  491. public static TType GetOriginalValue<T, TType>(this T sender, string propertyname) where T : BaseObject
  492. {
  493. return sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(propertyname)
  494. ? (TType)CoreUtils.ChangeType(sender.OriginalValueList[propertyname], typeof(TType))
  495. : default;
  496. }
  497. /// <summary>
  498. /// Get all database values (i.e., non-calculated, local properties) for a given object <paramref name="sender"/>.
  499. /// If <paramref name="all"/> is <see langword="false"/>, only retrieve values which have changed.
  500. /// </summary>
  501. public static Dictionary<string, object?> GetValues<T>(this T sender, bool all) where T : BaseObject
  502. {
  503. var result = new Dictionary<string, object?>();
  504. foreach(var property in DatabaseSchema.Properties(sender.GetType()))
  505. {
  506. if (property.IsDBColumn && (all || sender.HasOriginalValue(property.Name)))
  507. {
  508. result[property.Name] = property.Getter()(sender);
  509. }
  510. }
  511. return result;
  512. }
  513. public static Dictionary<string, object?> GetOriginaValues<T>(this T sender) where T : BaseObject
  514. {
  515. var result = new Dictionary<string, object?>();
  516. foreach(var property in DatabaseSchema.Properties(sender.GetType()))
  517. {
  518. if (property.IsDBColumn && sender.OriginalValueList.TryGetValue(property.Name, out var value))
  519. {
  520. result[property.Name] = value;
  521. }
  522. }
  523. return result;
  524. }
  525. public static BaseObjectSnapshot<T> TakeSnapshot<T>(this T obj)
  526. where T : BaseObject
  527. {
  528. return new BaseObjectSnapshot<T>(obj);
  529. }
  530. public static List<string> Compare<T>(this T sender, Dictionary<string, object> original) where T : BaseObject
  531. {
  532. var result = new List<string>();
  533. var current = GetValues(sender, true);
  534. foreach (var key in current.Keys)
  535. if (original.ContainsKey(key))
  536. {
  537. if (current[key] == null)
  538. {
  539. if (original[key] != null)
  540. result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
  541. }
  542. else
  543. {
  544. if (!current[key].Equals(original[key]))
  545. result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
  546. }
  547. }
  548. else
  549. {
  550. result.Add(string.Format("[{0}] not present in previous dictionary!", key));
  551. }
  552. return result;
  553. }
  554. public static bool GetValue<T,TType>(this T sender, Expression<Func<T,TType>> property, bool original, out TType result) where T : BaseObject
  555. {
  556. if (sender.HasOriginalValue<T, TType>(property))
  557. {
  558. if (original)
  559. result = sender.GetOriginalValue<T, TType>(property);
  560. else
  561. {
  562. var expr = property.Compile();
  563. result = expr(sender);
  564. }
  565. return true;
  566. }
  567. result = default(TType);
  568. return false;
  569. }
  570. public static void SetOriginalValue<T, TType>(this T sender, string propertyname, TType value) where T : BaseObject
  571. {
  572. sender.OriginalValueList[propertyname] = value;
  573. }
  574. public static bool HasOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
  575. {
  576. //var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  577. String propname = CoreUtils.GetFullPropertyName(property, ".");
  578. return !String.IsNullOrWhiteSpace(propname) && sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(propname);
  579. }
  580. public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
  581. {
  582. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  583. return prop != null && sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(prop.Name)
  584. ? (TType)CoreUtils.ChangeType(sender.OriginalValueList[prop.Name], typeof(TType))
  585. : default;
  586. }
  587. public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType defaultValue) where T : BaseObject
  588. {
  589. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  590. return prop != null && sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(prop.Name)
  591. ? (TType)CoreUtils.ChangeType(sender.OriginalValueList[prop.Name], typeof(TType))
  592. : defaultValue;
  593. }
  594. public static void SetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType value) where T : BaseObject
  595. {
  596. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  597. sender.OriginalValueList[prop.Name] = value;
  598. }
  599. }
  600. /// <summary>
  601. /// An <see cref="IProperty"/> is loggable if it has the <see cref="LoggablePropertyAttribute"/> defined on it.<br/>
  602. /// If it is part of an <see cref="IEntityLink"/>, then it is only loggable if the <see cref="IEntityLink"/> property on the parent class
  603. /// also has <see cref="LoggablePropertyAttribute"/>.
  604. /// </summary>
  605. public class LoggablePropertyAttribute : Attribute
  606. {
  607. public string Format { get; set; }
  608. }
  609. }