preliminary implementation of derived instances

This commit is contained in:
Michael Becker 2024-12-19 08:35:07 -05:00
parent 3a3e681a90
commit 784e04fd58
5 changed files with 255 additions and 14 deletions

@ -1 +1 @@
Subproject commit 54d5ab8b2c37bb9139eb6800631eece9b60ee25a
Subproject commit cdb956eab2cc70bebb94803486e96230e83c465e

View File

@ -19,6 +19,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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<InstanceHandle, object?> derivedData)
{
_oms = oms;
_inst = inst;
_parentClassInstance = oms.GetParentClass(inst);
Dictionary<InstanceHandle, object?> _derivedData = new Dictionary<InstanceHandle, object?>();
foreach (KeyValuePair<InstanceHandle, object?> 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<InstanceHandle, object?>? derivedData = null)
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
if (derivedData == null)
{
derivedData = GetDerivedData();
}
foreach (KeyValuePair<InstanceHandle, object?> 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<InstanceHandle, object?>? GetDerivedData()
{
Dictionary<InstanceHandle, object?> derivedData = new Dictionary<InstanceHandle, object?>();
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<InstanceHandle> 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;
}
}
}

View File

@ -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
{

View File

@ -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<string, InstanceHandle> _derivedInstances = new Dictionary<string, InstanceHandle>();
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<InstanceHandle, object?>();
IEnumerable<KeyValuePair<InstanceHandle, object?>>? kvps = ik.GetDerivedData();
if (kvps != null)
{
foreach (KeyValuePair<InstanceHandle, object?> 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<T>(InstanceHandle source, InstanceHandle attribute, T defaultValue = default(T), DateTime? effectiveDate = null)
private Dictionary<InstanceHandle, Dictionary<InstanceHandle, object?>?> _derivedData = new Dictionary<InstanceHandle, Dictionary<InstanceHandle, object?>?>();
public Dictionary<InstanceHandle, object?>? GetDerivedData(InstanceHandle source)
{
if (IsDerivedInstance(source))
{
if (!_derivedData.ContainsKey(source))
{
_derivedData[source] = new Dictionary<InstanceHandle, object?>();
}
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<T>(InstanceHandle source, InstanceHandle attribute, T defaultValue = default(T), DateTime? effectiveDate = null)
{
Dictionary<InstanceHandle, object>? 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<InstanceHandle, object>();
}
_derivedData[source][attribute] = value;
return;
}
SetAttributeValueInternal(source, attribute, value, effectiveDate.GetValueOrDefault(DateTime.Now));
}

View File

@ -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<InstanceHandle, object?>? dict = Oms.GetDerivedData(h2);
string attVName = Oms.GetAttributeValue<string>(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<string>(ih, Oms.GetInstance(KnownAttributeGuids.Text.Name));
Assert.That(attVName, Is.EqualTo(TEST_DERIVED_VALUE));
}
}