BaseObject.cs 24 KB

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