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));
+ }
+
+}