2023-10-31 11:47:27 -04:00

693 lines
21 KiB
C#
Executable File

//
// MochaClassLibraryDataFormat.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2020 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// 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 System.Collections.Generic;
using UniversalEditor.Accessors;
using UniversalEditor.IO;
using UniversalEditor.ObjectModels.FileSystem;
using UniversalEditor.Plugins.Mocha.ObjectModels.MochaClassLibrary;
namespace UniversalEditor.Plugins.Mocha.DataFormats.MochaBinary
{
public class MochaBinaryDataFormat : Slick.SlickBinaryDataFormat
{
private static DataFormatReference _dfr = null;
protected override DataFormatReference MakeReferenceInternal()
{
if (_dfr == null)
{
_dfr = base.MakeReferenceInternal();
_dfr.Capabilities.Clear();
_dfr.Capabilities.Add(typeof(MochaClassLibraryObjectModel), DataFormatCapabilities.All);
}
return _dfr;
}
protected override void BeforeLoadInternal(Stack<ObjectModel> objectModels)
{
base.BeforeLoadInternal(objectModels);
objectModels.Push(new FileSystemObjectModel());
}
private struct LIBRARY_INFO
{
public int libraryReferenceCount;
public int instanceCount;
public int attributeValueCount;
public int relationshipCount;
}
private struct INSTANCE_INFO
{
public int instanceIndex;
public int attributeValueCount;
}
protected override void AfterLoadInternal(Stack<ObjectModel> objectModels)
{
base.AfterLoadInternal(objectModels);
if (objectModels.Count < 2) throw new ObjectModelNotSupportedException("must have a FileSystemObjectModel and a MochaClassLibraryObjectModel in the stack");
FileSystemObjectModel fsom = (objectModels.Pop() as FileSystemObjectModel);
if (fsom == null) throw new ObjectModelNotSupportedException();
MochaClassLibraryObjectModel mcl = (objectModels.Pop() as MochaClassLibraryObjectModel);
if (mcl == null) throw new ObjectModelNotSupportedException();
List<Guid> _instanceGuids = new List<Guid>();
List<string> _stringTable = new List<string>();
LIBRARY_INFO[] library_info = null;
#region Libraries
{
File fLibraries = fsom.FindFile("Libraries");
using (MemoryAccessor ma = new MemoryAccessor(fLibraries.GetData()))
{
int libraryCount = ma.Reader.ReadInt32();
library_info = new LIBRARY_INFO[libraryCount];
for (int i = 0; i < libraryCount; i++)
{
MochaLibrary library = new MochaLibrary();
library.ID = ma.Reader.ReadGuid();
library_info[i].libraryReferenceCount = ma.Reader.ReadInt32();
library_info[i].instanceCount = ma.Reader.ReadInt32();
// library_info[i].attributeValueCount = ma.Reader.ReadInt32();
library_info[i].relationshipCount = ma.Reader.ReadInt32();
mcl.Libraries.Add(library);
}
}
}
#endregion
#region Guid Table
{
File fGlobalIdentifiers = fsom.FindFile("GlobalIdentifiers");
using (MemoryAccessor ma = new MemoryAccessor(fGlobalIdentifiers.GetData()))
{
int instanceCount = ma.Reader.ReadInt32();
for (int i = 0; i < instanceCount; i++)
{
_instanceGuids.Add(ma.Reader.ReadGuid());
}
}
}
#endregion
#region String Table
{
File f = fsom.FindFile("StringTable");
using (MemoryAccessor ma = new MemoryAccessor(f.GetData()))
{
int stringTableCount = ma.Reader.ReadInt32();
for (int i = 0; i < stringTableCount; i++)
{
string value = ma.Reader.ReadNullTerminatedString();
_stringTable.Add(value);
}
}
}
#endregion
INSTANCE_INFO[] instance_info = null;
#region Instances
{
File f = fsom.FindFile("Instances");
using (MemoryAccessor ma = new MemoryAccessor(f.GetData()))
{
for (int i = 0; i < mcl.Libraries.Count; i++)
{
int instance_info_offset = 0; // fixme: this should not be needed
int libraryReferenceCount = ma.Reader.ReadInt32();
for (int j = 0; j < libraryReferenceCount; j++)
{
int libraryIndex = ma.Reader.ReadInt32();
Guid libraryID = _instanceGuids[libraryIndex];
mcl.Libraries[i].LibraryReferences.Add(libraryID);
}
int instanceCount = ma.Reader.ReadInt32();
// if (instance_info == null)
{
instance_info = new INSTANCE_INFO[instanceCount];
}
/*
else
{
instance_info_offset = instance_info.Length;
Array.Resize<INSTANCE_INFO>(ref instance_info, instance_info.Length + instanceCount);
}
*/
for (int j = 0; j < instanceCount; j++)
{
instance_info[j + instance_info_offset].instanceIndex = ma.Reader.ReadInt32();
instance_info[j + instance_info_offset].attributeValueCount = ma.Reader.ReadInt32();
MochaInstanceFlags flags = (MochaInstanceFlags)ma.Reader.ReadInt32();
int? index = null;
if ((flags & MochaInstanceFlags.HasIndex) == MochaInstanceFlags.HasIndex)
{
index = ma.Reader.ReadInt32();
}
MochaInstance inst = new MochaInstance();
inst.ID = _instanceGuids[instance_info[j + instance_info_offset].instanceIndex];
inst.Index = index;
mcl.Libraries[i].Instances.Add(inst);
}
}
}
}
#endregion
#region Attributes
{
File f = fsom.FindFile("Attributes");
using (MemoryAccessor ma = new MemoryAccessor(f.GetData()))
{
for (int i = 0; i < mcl.Libraries.Count; i++)
{
for (int j = 0; j < mcl.Libraries[i].Instances.Count; j++)
{
for (int k = 0; k < instance_info[j].attributeValueCount; k++)
{
int attributeInstanceIndex = ma.Reader.ReadInt32();
MochaAttributeValue val = new MochaAttributeValue();
val.AttributeInstanceID = _instanceGuids[attributeInstanceIndex];
val.Value = ReadMochaAttributeValue(ma.Reader, _stringTable);
mcl.Libraries[i].Instances[j].AttributeValues.Add(val);
}
}
}
}
}
#endregion
#region Relationships
{
File f = fsom.FindFile("Relationships");
using (MemoryAccessor ma = new MemoryAccessor(f.GetData()))
{
for (int i = 0; i < mcl.Libraries.Count; i++)
{
for (int j = 0; j < library_info[i].relationshipCount; j++)
{
int relationshipIndex = ma.Reader.ReadInt32();
int sourceInstanceIndex = ma.Reader.ReadInt32();
MochaRelationship rel = new MochaRelationship();
rel.RelationshipInstanceID = _instanceGuids[relationshipIndex];
rel.SourceInstanceID = _instanceGuids[sourceInstanceIndex];
int targetInstanceCount = ma.Reader.ReadInt32();
for (int k = 0; k < targetInstanceCount; k++)
{
int instanceIndex = ma.Reader.ReadInt32();
rel.DestinationInstanceIDs.Add(_instanceGuids[instanceIndex]);
}
mcl.Libraries[i].Relationships.Add(rel);
}
}
}
}
#endregion
#region Tenants
{
File f = fsom.FindFile("Tenants");
if (f != null)
{
using (MemoryAccessor ma = new MemoryAccessor(f.GetData()))
{
int tenantCount = ma.Reader.ReadInt32();
for (int i = 0; i < tenantCount; i++)
{
int instanceIndex = ma.Reader.ReadInt32();
Guid instanceGuid = _instanceGuids[instanceIndex];
int tenantNameIndex = ma.Reader.ReadInt32();
string tenantName = _stringTable[tenantNameIndex];
MochaTenant tenant = new MochaTenant();
tenant.ID = instanceGuid;
tenant.Name = tenantName;
int libraryReferenceCount = ma.Reader.ReadInt32();
for (int j = 0; j < libraryReferenceCount; j++)
{
int libraryIndex = ma.Reader.ReadInt32();
Guid libraryID = _instanceGuids[libraryIndex];
tenant.LibraryReferences.Add(libraryID);
}
int instanceCount = ma.Reader.ReadInt32();
for (int j = 0; j < instanceCount; j++)
{
int instanceIndex2 = ma.Reader.ReadInt32();
MochaInstance inst = new MochaInstance();
inst.ID = _instanceGuids[instanceIndex2];
tenant.Instances.Add(inst);
}
int relationshipCount = ma.Reader.ReadInt32();
for (int j = 0; j < relationshipCount; j++)
{
int sourceInex = ma.Reader.ReadInt32();
Guid ssource = _instanceGuids[sourceInex];
int relationshipInex = ma.Reader.ReadInt32();
Guid relati = _instanceGuids[relationshipInex];
MochaRelationship rel = new MochaRelationship();
rel.SourceInstanceID = ssource;
rel.RelationshipInstanceID = relati;
int count = ma.Reader.ReadInt32();
for (int k = 0; k < count; k++)
{
int targetIndex = ma.Reader.ReadInt32();
Guid targ = _instanceGuids[targetIndex];
rel.DestinationInstanceIDs.Add(targ);
}
tenant.Relationships.Add(rel);
}
mcl.Tenants.Add(tenant);
}
ma.Close();
f.SetData(ma.ToArray());
}
}
}
#endregion
#region Journal
{
// journal is present in MCX / MCD (application, data files)
// is an opcode based format
// eg. 0x01 = create instance, 0x02 = delete instance
// 0x04 = set attribute value, 0x05 = delete attribute value
// 0x08 = create relationship, 0x09 = remove relationship
File f = fsom.FindFile("Journal");
if (f != null)
{
using (MemoryAccessor ma = new MemoryAccessor(f.GetData()))
{
for (int i = 0; i < mcl.Tenants.Count; i++)
{
bool readingTenant = true;
while (readingTenant)
{
MochaOpcode opcode = (MochaOpcode)ma.Reader.ReadByte();
switch (opcode)
{
case MochaOpcode.BeginTenant:
{
int tenantNameIndex = ma.Reader.ReadInt32();
break;
}
case MochaOpcode.EndTenant:
{
readingTenant = false; // exit outer loop
break;
}
case MochaOpcode.CreateInstance:
{
int guidIndex = ma.Reader.ReadInt32();
MochaInstance inst = new MochaInstance();
inst.ID = _instanceGuids[guidIndex];
mcl.Tenants[i].Instances.Add(inst);
break;
}
case MochaOpcode.CreateRelationship:
case MochaOpcode.RemoveRelationship:
{
int relationshipIndex = ma.Reader.ReadInt32();
int sourceInstanceIndex = ma.Reader.ReadInt32();
MochaRelationship rel = new MochaRelationship();
rel.RelationshipInstanceID = _instanceGuids[relationshipIndex];
rel.SourceInstanceID = _instanceGuids[sourceInstanceIndex];
int targetInstanceCount = ma.Reader.ReadInt32();
for (int k = 0; k < targetInstanceCount; k++)
{
int instanceIndex = ma.Reader.ReadInt32();
rel.DestinationInstanceIDs.Add(_instanceGuids[instanceIndex]);
}
if (opcode == MochaOpcode.RemoveRelationship)
{
rel.Remove = true;
}
mcl.Tenants[i].Relationships.Add(rel);
break;
}
case MochaOpcode.SetAttributeValue:
{
int instanceIndex = ma.Reader.ReadInt32();
Guid instanceID = _instanceGuids[instanceIndex];
int attributeIndex = ma.Reader.ReadInt32();
Guid attributeInstanceID = _instanceGuids[attributeIndex];
object value = ReadMochaAttributeValue(ma.Reader, _stringTable);
MochaAttributeValue mav = new MochaAttributeValue();
mav.AttributeInstanceID = attributeInstanceID;
mav.Value = value;
mcl.Tenants[i].Instances[instanceID].AttributeValues.Add(mav);
break;
}
}
}
}
}
}
}
#endregion
}
private object ReadMochaAttributeValue(Reader reader, List<string>_stringTable)
{
MochaAttributeType attributeType = (MochaAttributeType)reader.ReadInt32();
switch (attributeType)
{
case MochaAttributeType.None:
{
return null;
}
case MochaAttributeType.Text:
{
int stringTableIndex = reader.ReadInt32();
string value = _stringTable[stringTableIndex];
return value;
}
case MochaAttributeType.Boolean:
{
bool value = reader.ReadBoolean();
return value;
}
case MochaAttributeType.Date:
{
DateTime value = reader.ReadDateTime();
return value;
}
case MochaAttributeType.Numeric:
{
decimal value = reader.ReadDecimal();
return value;
}
case MochaAttributeType.Unknown:
{
break;
}
}
throw new NotSupportedException();
}
protected override void BeforeSaveInternal(Stack<ObjectModel> objectModels)
{
base.BeforeSaveInternal(objectModels);
FileNameSize = 32;
MochaClassLibraryObjectModel mcl = (objectModels.Pop() as MochaClassLibraryObjectModel);
if (mcl == null) throw new ObjectModelNotSupportedException();
FileSystemObjectModel fsom = new FileSystemObjectModel();
List<Guid> _instanceIndices = new List<Guid>();
List<string> _stringTable = new List<string>();
#region Libraries
{
File f = fsom.AddFile("Libraries");
MemoryAccessor ma = new MemoryAccessor();
ma.Writer.WriteInt32(mcl.Libraries.Count);
for (int i = 0; i < mcl.Libraries.Count; i++)
{
ma.Writer.WriteGuid(mcl.Libraries[i].ID);
ma.Writer.WriteInt32(mcl.Libraries[i].LibraryReferences.Count);
ma.Writer.WriteInt32(mcl.Libraries[i].Instances.Count);
ma.Writer.WriteInt32(mcl.Libraries[i].Relationships.Count);
}
ma.Close();
f.SetData(ma.ToArray());
}
#endregion
#region Instances
{
File f = fsom.AddFile("Instances");
MemoryAccessor ma = new MemoryAccessor();
for (int i = 0; i < mcl.Libraries.Count; i++)
{
ma.Writer.WriteInt32(mcl.Libraries[i].LibraryReferences.Count);
for (int j = 0; j < mcl.Libraries[i].LibraryReferences.Count; j++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, mcl.Libraries[i].LibraryReferences[j]));
}
ma.Writer.WriteInt32(mcl.Libraries[i].Instances.Count);
for (int j = 0; j < mcl.Libraries[i].Instances.Count; j++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, mcl.Libraries[i].Instances[j].ID));
ma.Writer.WriteInt32(mcl.Libraries[i].Instances[j].AttributeValues.Count);
MochaInstanceFlags flags = MochaInstanceFlags.None;
if (mcl.Libraries[i].Instances[j].Index != null)
{
flags |= MochaInstanceFlags.HasIndex;
}
ma.Writer.WriteInt32((int)flags);
if ((flags & MochaInstanceFlags.HasIndex) == MochaInstanceFlags.HasIndex)
{
ma.Writer.WriteInt32(mcl.Libraries[i].Instances[j].Index.Value);
}
}
}
ma.Close();
f.SetData(ma.ToArray());
}
#endregion
#region Attributes
{
File f = fsom.AddFile("Attributes");
MemoryAccessor ma = new MemoryAccessor();
for (int i = 0; i < mcl.Libraries.Count; i++)
{
/*
writer.WriteInt32(mcl.Libraries[i].Metadata.Count);
for (int j = 0; j < mcl.Libraries[i].Metadata.Count; j++)
{
writer.WriteNullTerminatedString(mcl.Libraries[i].Metadata[j].Name);
writer.WriteNullTerminatedString(mcl.Libraries[i].Metadata[j].Value);
}
*/
for (int j = 0; j < mcl.Libraries[i].Instances.Count; j++)
{
for (int k = 0; k < mcl.Libraries[i].Instances[j].AttributeValues.Count; k++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, mcl.Libraries[i].Instances[j].AttributeValues[k].AttributeInstanceID));
WriteMochaAttributeValue(ma.Writer, _stringTable, mcl.Libraries[i].Instances[j].AttributeValues[k].Value);
}
}
}
ma.Close();
f.SetData(ma.ToArray());
}
#endregion
#region Relationships
{
File f = fsom.AddFile("Relationships");
MemoryAccessor ma = new MemoryAccessor();
for (int i = 0; i < mcl.Libraries.Count; i++)
{
for (int j = 0; j < mcl.Libraries[i].Relationships.Count; j++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, mcl.Libraries[i].Relationships[j].RelationshipInstanceID));
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, mcl.Libraries[i].Relationships[j].SourceInstanceID));
ma.Writer.WriteInt32(mcl.Libraries[i].Relationships[j].DestinationInstanceIDs.Count);
for (int k = 0; k < mcl.Libraries[i].Relationships[j].DestinationInstanceIDs.Count; k++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, mcl.Libraries[i].Relationships[j].DestinationInstanceIDs[k]));
}
}
}
ma.Close();
f.SetData(ma.ToArray());
}
#endregion
#region Tenants
{
if (mcl.Tenants.Count > 0)
{
File f = fsom.AddFile("Tenants");
MemoryAccessor ma = new MemoryAccessor();
ma.Writer.WriteInt32(mcl.Tenants.Count);
foreach (MochaTenant tenant in mcl.Tenants)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, tenant.ID));
ma.Writer.WriteInt32(AddOrUpdateLookupTable<string>(_stringTable, tenant.Name));
ma.Writer.WriteInt32(tenant.LibraryReferences.Count);
for (int i = 0; i < tenant.LibraryReferences.Count; i++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, tenant.LibraryReferences[i]));
}
ma.Writer.WriteInt32(tenant.Instances.Count);
for (int i = 0; i < tenant.Instances.Count; i++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, tenant.Instances[i].ID));
}
ma.Writer.WriteInt32(tenant.Relationships.Count);
for (int i = 0; i < tenant.Relationships.Count; i++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, tenant.Relationships[i].SourceInstanceID));
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, tenant.Relationships[i].RelationshipInstanceID));
ma.Writer.WriteInt32(tenant.Relationships[i].DestinationInstanceIDs.Count);
for (int j = 0; j < tenant.Relationships[i].DestinationInstanceIDs.Count; j++)
{
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, tenant.Relationships[i].DestinationInstanceIDs[j]));
}
}
}
ma.Close();
f.SetData(ma.ToArray());
}
}
#endregion
#region Journal
{
if (mcl.Tenants.Count > 0)
{
File f = fsom.AddFile("Journal");
MemoryAccessor ma = new MemoryAccessor();
foreach (MochaTenant tenant in mcl.Tenants)
{
ma.Writer.WriteByte((byte)MochaOpcode.BeginTenant);
ma.Writer.WriteInt32(AddOrUpdateLookupTable<string>(_stringTable, tenant.Name));
for (int i = 0; i < tenant.Instances.Count; i++)
{
for (int j = 0; j < tenant.Instances[i].AttributeValues.Count; j++)
{
ma.Writer.WriteByte((byte)MochaOpcode.SetAttributeValue);
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, tenant.Instances[i].ID));
ma.Writer.WriteInt32(AddOrUpdateLookupTable<Guid>(_instanceIndices, tenant.Instances[i].AttributeValues[j].AttributeInstanceID));
WriteMochaAttributeValue(ma.Writer, _stringTable, tenant.Instances[i].AttributeValues[j].Value);
}
}
ma.Writer.WriteByte((byte)MochaOpcode.EndTenant);
}
ma.Close();
f.SetData(ma.ToArray());
}
}
#endregion
#region Guid Table
{
File f = fsom.AddFile("GlobalIdentifiers");
MemoryAccessor ma = new MemoryAccessor();
ma.Writer.WriteInt32(_instanceIndices.Count);
foreach (Guid id in _instanceIndices)
{
ma.Writer.WriteGuid(id);
}
ma.Close();
f.SetData(ma.ToArray());
}
#endregion
#region String Table
{
File f = fsom.AddFile("StringTable");
MemoryAccessor ma = new MemoryAccessor();
ma.Writer.WriteInt32(_stringTable.Count);
foreach (string id in _stringTable)
{
ma.Writer.WriteNullTerminatedString(id);
}
ma.Close();
f.SetData(ma.ToArray());
}
#endregion
objectModels.Push(fsom);
}
private void WriteMochaAttributeValue(Writer writer, List<string> _stringTable, object value)
{
if (value == null)
{
writer.WriteInt32((int)MochaAttributeType.None);
}
else if (value is string)
{
writer.WriteInt32((int)MochaAttributeType.Text);
writer.WriteInt32(AddOrUpdateLookupTable<string>(_stringTable, value as string));
}
else if (value is bool)
{
writer.WriteInt32((int)MochaAttributeType.Boolean);
writer.WriteBoolean((bool)value);
}
else if (value is DateTime)
{
writer.WriteInt32((int)MochaAttributeType.Date);
writer.WriteDateTime((DateTime)value);
}
else if (value is decimal || value is int)
{
writer.WriteInt32((int)MochaAttributeType.Numeric);
// ...... eww.
writer.WriteDecimal(Decimal.Parse(value.ToString()));
}
else
{
writer.WriteInt32((int)MochaAttributeType.Unknown);
writer.WriteObject(value);
}
}
private int AddOrUpdateLookupTable<T1>(List<T1> dict, T1 id)
{
if (!dict.Contains(id))
{
dict.Add(id);
}
return dict.IndexOf(id);
}
}
}