diff --git a/mocha-common b/mocha-common index 54d5ab8..cdb956e 160000 --- a/mocha-common +++ b/mocha-common @@ -1 +1 @@ -Subproject commit 54d5ab8b2c37bb9139eb6800631eece9b60ee25a +Subproject commit cdb956eab2cc70bebb94803486e96230e83c465e diff --git a/mocha-dotnet/src/lib/Mocha.Core/InstanceKey.cs b/mocha-dotnet/src/lib/Mocha.Core/InstanceKey.cs index 4ae1b9c..2d9438d 100755 --- a/mocha-dotnet/src/lib/Mocha.Core/InstanceKey.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/InstanceKey.cs @@ -19,6 +19,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . using System; +using MBS.Core; + namespace Mocha.Core { public struct InstanceKey @@ -29,7 +31,25 @@ namespace Mocha.Core private bool _isNotEmpty; public bool IsEmpty { get { return !_isNotEmpty; } } - public bool IsDerived { get; } + public bool IsDerived { get { return _derivedDataString != null; } } + + internal Oms? _oms; + internal InstanceHandle _inst; + internal InstanceHandle _parentClassInstance; + private string? _derivedDataString = null; + internal void SetDerivedData(Oms oms, InstanceHandle inst, Dictionary derivedData) + { + _oms = oms; + _inst = inst; + _parentClassInstance = oms.GetParentClass(inst); + + Dictionary _derivedData = new Dictionary(); + foreach (KeyValuePair kvp in derivedData) + { + _derivedData[kvp.Key] = kvp.Value; + } + _derivedDataString = GetDerivedDataString(_derivedData); + } public static readonly InstanceKey Empty = new InstanceKey(); @@ -58,7 +78,6 @@ namespace Mocha.Core { ClassIndex = 0; InstanceIndex = 0; - IsDerived = false; _isNotEmpty = false; } else @@ -67,32 +86,50 @@ namespace Mocha.Core if (instanceKey.Contains("$")) { splitChar = '$'; - IsDerived = false; } else if (instanceKey.Contains("!")) { splitChar = '!'; - IsDerived = true; } if (splitChar != '\0') { - string[] split = instanceKey.Split(new char[] { '$' }); + string[] split = instanceKey.Split(new char[] { splitChar }); if (split.Length == 2) { - if (Int32.TryParse(split[0], out int ci) && Int32.TryParse(split[1], out int ii)) + if (splitChar == '!') { - ClassIndex = ci; - InstanceIndex = ii; - _isNotEmpty = true; + if (Int32.TryParse(split[0], out int ci)) + { + ClassIndex = ci; + InstanceIndex = -1; + SetDerivedDataString(split[1]); + _isNotEmpty = true; + } return; } + else + { + if (Int32.TryParse(split[0], out int ci) && Int32.TryParse(split[1], out int ii)) + { + ClassIndex = ci; + InstanceIndex = ii; + _isNotEmpty = true; + return; + } + } } } } throw new ArgumentException("must be a string containing two integers separated by a '$' or a '!'"); } - public InstanceKey(int classIndex, int instanceIndex) + + private void SetDerivedDataString(string base64String) + { + _derivedDataString = base64String; + } + + public InstanceKey(int classIndex, int instanceIndex) { ClassIndex = classIndex; InstanceIndex = instanceIndex; @@ -116,9 +153,70 @@ namespace Mocha.Core { if (IsDerived) { - return String.Format("{0}!{1}", ClassIndex, InstanceIndex); + return String.Format("{0}!{1}", ClassIndex, _derivedDataString); } return String.Format("{0}${1}", ClassIndex, InstanceIndex); } - } + + internal string GetDerivedDataString(Dictionary? derivedData = null) + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + + if (derivedData == null) + { + derivedData = GetDerivedData(); + } + + foreach (KeyValuePair kvp in derivedData) + { + if (kvp.Value is string) + { + string value = (string)kvp.Value; + bw.Write((int)value.Length); + bw.Write((string)value); + } + } + + bw.Flush(); + bw.Close(); + byte[] data = ms.ToArray(); + + string b64 = Convert.ToBase64String(data); + return b64; + } + + internal Dictionary? GetDerivedData() + { + Dictionary derivedData = new Dictionary(); + + InstanceKey ikParentClass = new InstanceKey(1, ClassIndex); + _parentClassInstance = _oms.GetInstance(ikParentClass); + + if (_derivedDataString != null) + { + byte[] data = Convert.FromBase64String(_derivedDataString); + MemoryStream ms = new MemoryStream(data); + BinaryReader br = new BinaryReader(ms); + + IEnumerable attributes = _oms.GetRelatedInstances(_parentClassInstance, _oms.GetInstance(KnownRelationshipGuids.Class__has__Attribute)); + foreach (InstanceHandle att in attributes) + { + if (_oms.IsInstanceOf(att, _oms.GetInstance(KnownInstanceGuids.Classes.TextAttribute))) + { + if (br.BaseStream.EndOfStream()) + { + break; + } + + int length = br.ReadInt32(); + string value = br.ReadString(); + derivedData[att] = value; + } + } + } + + return derivedData; + } + } } diff --git a/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs b/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs index b82073c..b713f57 100755 --- a/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs @@ -60,6 +60,7 @@ namespace Mocha.Core public static Guid UseAnyCondition { get; } = new Guid("{31a8a2c2-1f55-4dfe-b177-427a2219ef8c}"); public static Guid ValidateOnlyOnSubmit { get; } = new Guid("{400fcd8e-823b-4f4a-aa38-b444f763259b}"); public static Guid UserIsLoggedIn { get; } = new Guid("{8e93d9f3-a897-4c97-935c-b3427f90633b}"); + public static Guid Derived { get; } = new Guid("{66991ca1-ef08-4f30-846c-4984c2a3139d}"); } public static class Numeric { diff --git a/mocha-dotnet/src/lib/Mocha.Core/Oms.cs b/mocha-dotnet/src/lib/Mocha.Core/Oms.cs index 19cdcba..da9156d 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/Oms.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/Oms.cs @@ -301,6 +301,15 @@ public abstract class Oms { return new InstanceKey(1, 1); } + if (IsDerivedInstance(instance)) + { + InstanceHandle parent = GetParentClass(instance); + InstanceKey ikParent = GetInstanceKeyInternal(parent); + + InstanceKey ik = new InstanceKey(ikParent.InstanceIndex, -1); + ik.SetDerivedData(this, instance, GetDerivedData(instance)); + return ik; + } return GetInstanceKeyInternal(instance); } @@ -388,8 +397,42 @@ public abstract class Oms } throw new KeyNotFoundException(String.Format("inst not found: {0}", globalIdentifier)); } + + private Dictionary _derivedInstances = new Dictionary(); public InstanceHandle GetInstance(InstanceKey ik) { + if (ik.IsDerived) + { + ik._oms = this; + InstanceHandle inst; + if (!_derivedInstances.ContainsKey(ik.ToString())) + { + InstanceHandle pclass = GetInstance(new InstanceKey(1, ik.ClassIndex)); + + inst = InstanceHandle.Create(); + _derivedInstances[ik.ToString()] = inst; + + SetParentClass(inst, pclass); + ik._inst = inst; + ik._parentClassInstance = pclass; + + _derivedData[inst] = new Dictionary(); + IEnumerable>? kvps = ik.GetDerivedData(); + if (kvps != null) + { + foreach (KeyValuePair kvp in kvps) + { + _derivedData[inst][kvp.Key] = kvp.Value; + } + } + } + else + { + inst = _derivedInstances[ik.ToString()]; + } + return inst; + } + if (TryGetInstance(ik, out InstanceHandle ih)) { return ih; @@ -584,8 +627,43 @@ public abstract class Oms value = default(T); return false; } - public T GetAttributeValue(InstanceHandle source, InstanceHandle attribute, T defaultValue = default(T), DateTime? effectiveDate = null) + + private Dictionary?> _derivedData = new Dictionary?>(); + public Dictionary? GetDerivedData(InstanceHandle source) { + if (IsDerivedInstance(source)) + { + if (!_derivedData.ContainsKey(source)) + { + _derivedData[source] = new Dictionary(); + } + return _derivedData[source]; + } + return null; + } + + public bool IsDerivedInstance(InstanceHandle source) + { + // !!! we cannot use GetAttributeValue here !!! + InstanceHandle pclass = GetParentClass(source); + object? o = GetAttributeValueInternal(pclass, GetInstance(KnownAttributeGuids.Boolean.Derived), DateTime.Now); + if (o is bool b && b) + { + return true; + } + return false; + } + + public T GetAttributeValue(InstanceHandle source, InstanceHandle attribute, T defaultValue = default(T), DateTime? effectiveDate = null) + { + Dictionary? derivedData = GetDerivedData(source); + if (derivedData != null) + { + if (derivedData.ContainsKey(attribute)) + { + return (T)derivedData[attribute]; + } + } if (TryGetAttributeValue(source, attribute, effectiveDate, out T value)) { return value; @@ -596,6 +674,16 @@ public abstract class Oms public void SetAttributeValue(InstanceHandle source, InstanceHandle attribute, object value, DateTime? effectiveDate = null) { ValidateConstraintsForAttribute(source, attribute, value); + + if (IsDerivedInstance(source)) + { + if (!_derivedData.ContainsKey(source)) + { + _derivedData[source] = new Dictionary(); + } + _derivedData[source][attribute] = value; + return; + } SetAttributeValueInternal(source, attribute, value, effectiveDate.GetValueOrDefault(DateTime.Now)); } diff --git a/mocha-dotnet/tests/Mocha.Core.Tests/DerivedInstanceTests.cs b/mocha-dotnet/tests/Mocha.Core.Tests/DerivedInstanceTests.cs new file mode 100644 index 0000000..0455344 --- /dev/null +++ b/mocha-dotnet/tests/Mocha.Core.Tests/DerivedInstanceTests.cs @@ -0,0 +1,54 @@ +using System; +using Newtonsoft.Json.Serialization; + +namespace Mocha.Core.Tests; + +public class DerivedInstanceTests : OmsTestsBase +{ + InstanceHandle TEST_CLASS_DERIVED; + protected override void AfterSetup() + { + base.AfterSetup(); + + TEST_CLASS_DERIVED = Oms.CreateClass("Test Class (Derived)"); + Oms.SetAttributeValue(TEST_CLASS_DERIVED, Oms.GetInstance(KnownAttributeGuids.Boolean.Derived), true); + + // Derived classes are represented only by InstanceKeys with the form {ClassId}!{DerivedData}, where + // {DerivedData} is a Base64 string encoding all the attributes and relationships associated with the + // derived instance. + + // ? Should we be concerned with memory leaks given the lazy implementation of GetInstance on a derived + // ? InstanceKey essentially allocates a new InstanceHandle each time it's called ??? + Oms.AddAttribute(TEST_CLASS_DERIVED, Oms.GetInstance(KnownAttributeGuids.Text.Name)); + } + + const string TEST_DERIVED_VALUE = "Test Class Derived #1"; + + [Test] + public void Derived_Instance_Test_get_Text_Attribute_Value() + { + InstanceHandle iTestClassDerived = Oms.CreateInstanceOf(TEST_CLASS_DERIVED); + Oms.SetAttributeValue(iTestClassDerived, Oms.GetInstance(KnownAttributeGuids.Text.Name), TEST_DERIVED_VALUE); + + InstanceKey ik = Oms.GetInstanceKey(iTestClassDerived); + + InstanceHandle h2 = Oms.GetInstance(ik); + Dictionary? dict = Oms.GetDerivedData(h2); + + string attVName = Oms.GetAttributeValue(h2, Oms.GetInstance(KnownAttributeGuids.Text.Name)); + Assert.That(attVName, Is.EqualTo(TEST_DERIVED_VALUE)); + } + + [Test] + public void Derived_Instance_Test__instance_key_Parse() + { + string ikStr = "154!FQAAABVUZXN0IENsYXNzIERlcml2ZWQgIzE="; + InstanceKey ik = InstanceKey.Parse(ikStr); + + InstanceHandle ih = Oms.GetInstance(ik); + + string attVName = Oms.GetAttributeValue(ih, Oms.GetInstance(KnownAttributeGuids.Text.Name)); + Assert.That(attVName, Is.EqualTo(TEST_DERIVED_VALUE)); + } + +}