// // MochaClassLibraryDataFormat.cs // // Author: // Michael Becker // // 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 . 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 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 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 _instanceGuids = new List(); List _stringTable = new List(); 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(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_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 objectModels) { base.BeforeSaveInternal(objectModels); FileNameSize = 32; MochaClassLibraryObjectModel mcl = (objectModels.Pop() as MochaClassLibraryObjectModel); if (mcl == null) throw new ObjectModelNotSupportedException(); FileSystemObjectModel fsom = new FileSystemObjectModel(); List _instanceIndices = new List(); List _stringTable = new List(); #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(_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(_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(_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(_instanceIndices, mcl.Libraries[i].Relationships[j].RelationshipInstanceID)); ma.Writer.WriteInt32(AddOrUpdateLookupTable(_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(_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(_instanceIndices, tenant.ID)); ma.Writer.WriteInt32(AddOrUpdateLookupTable(_stringTable, tenant.Name)); ma.Writer.WriteInt32(tenant.LibraryReferences.Count); for (int i = 0; i < tenant.LibraryReferences.Count; i++) { ma.Writer.WriteInt32(AddOrUpdateLookupTable(_instanceIndices, tenant.LibraryReferences[i])); } ma.Writer.WriteInt32(tenant.Instances.Count); for (int i = 0; i < tenant.Instances.Count; i++) { ma.Writer.WriteInt32(AddOrUpdateLookupTable(_instanceIndices, tenant.Instances[i].ID)); } ma.Writer.WriteInt32(tenant.Relationships.Count); for (int i = 0; i < tenant.Relationships.Count; i++) { ma.Writer.WriteInt32(AddOrUpdateLookupTable(_instanceIndices, tenant.Relationships[i].SourceInstanceID)); ma.Writer.WriteInt32(AddOrUpdateLookupTable(_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(_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(_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(_instanceIndices, tenant.Instances[i].ID)); ma.Writer.WriteInt32(AddOrUpdateLookupTable(_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 _stringTable, object value) { if (value == null) { writer.WriteInt32((int)MochaAttributeType.None); } else if (value is string) { writer.WriteInt32((int)MochaAttributeType.Text); writer.WriteInt32(AddOrUpdateLookupTable(_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(List dict, T1 id) { if (!dict.Contains(id)) { dict.Add(id); } return dict.IndexOf(id); } } }