/** * @file kDat5Serializer.cpp * * @internal * Copyright (C) 2008-2021 by LMI Technologies Inc. * Licensed under the MIT License. * Redistributed files must retain the above copyright notice. */ #include #include #include #include #include #include kBeginValueEx(k, xkDat5SerializerTypeInfo) kAddField(xkDat5SerializerTypeInfo, kType, type) kAddField(xkDat5SerializerTypeInfo, kPointer, version) kAddField(xkDat5SerializerTypeInfo, k32u, id) kEndValueEx() kBeginFullClassEx(k, kDat5Serializer) kAddVMethod(kDat5Serializer, kObject, VRelease) kAddVMethod(kDat5Serializer, kSerializer, VInit) kAddPrivateVMethod(kDat5Serializer, kSerializer, VReset) kAddVMethod(kDat5Serializer, kSerializer, VWriteObject) kAddVMethod(kDat5Serializer, kSerializer, VReadObject) kAddPrivateVMethod(kDat5Serializer, kSerializer, VWriteType) kAddPrivateVMethod(kDat5Serializer, kSerializer, VReadType) kEndFullClassEx() kFx(kStatus) xkDat5Serializer_InitStatic() { kStaticObj(kDat5Serializer); kCheck(kLock_ConstructEx(&sobj->lock, xkLOCK_OPTION_PRIORITY_INHERITANCE, kNULL)); kCheck(kMap_Construct(&sobj->guidToInfo, kTypeOf(k32u), kTypeOf(xkDat5SerializerTypeInfo), 256, kNULL)); kCheck(kMap_Construct(&sobj->guidToCompressor, kTypeOf(kCompressionType), kTypeOf(kPointer), 0, kNULL)); kCheck(kAssembly_AddLoadHandler(xkDat5Serializer_OnAssemblyLoad, kNULL)); kCheck(kAssembly_AddUnloadHandler(xkDat5Serializer_OnAssemblyUnload, kNULL)); return kOK; } kFx(kStatus) xkDat5Serializer_ReleaseStatic() { kStaticObj(kDat5Serializer); kCheck(kAssembly_RemoveLoadHandler(xkDat5Serializer_OnAssemblyLoad, kNULL)); kCheck(kAssembly_RemoveUnloadHandler(xkDat5Serializer_OnAssemblyUnload, kNULL)); kCheck(kDestroyRef(&sobj->guidToCompressor)); kCheck(kDestroyRef(&sobj->guidToInfo)); kCheck(kDestroyRef(&sobj->lock)); return kOK; } kFx(kStatus) xkDat5Serializer_OnAssemblyLoad(kPointer receiver, kAssembly assembly, void* args) { kStaticObj(kDat5Serializer); kSize i, j; kCheck(kLock_Enter(sobj->lock)); kTry { //build serialization guid-to-info map if (kObject_IsShared(sobj->guidToInfo)) { kMap clone = kNULL; kTest(kObject_Clone(&clone, sobj->guidToInfo, kNULL)); kTest(kObject_Destroy(sobj->guidToInfo)); sobj->guidToInfo = clone; } for (i = 0; i < kAssembly_TypeCount(assembly); ++i) { kType type = kAssembly_TypeAt(assembly, i); for (j = 0; j < kType_VersionCount(type); ++j) { kTypeVersion version = kType_VersionAt(type, j); if (kStrEquals(kType_VersionFormat(type, version), xkDAT5_FORMAT)) { xkDat5SerializerTypeInfo info; k32u typeId, typeVersionId; info.type = type; info.version = version; if (sscanf(kType_VersionGuid(type, version), "%u-%u", &typeId, &typeVersionId) == 2) { info.id = xkDat5Guid_Create(typeId, typeVersionId); kTest(kMap_ReplaceT(sobj->guidToInfo, &info.id, &info)); } } } } //build compression guid-to-type map if (kObject_IsShared(sobj->guidToCompressor)) { kMap clone = kNULL; kTest(kObject_Clone(&clone, sobj->guidToCompressor, kNULL)); kTest(kObject_Destroy(sobj->guidToCompressor)); sobj->guidToCompressor = clone; } for (i = 0; i < kAssembly_TypeCount(assembly); ++i) { kType type = kAssembly_TypeAt(assembly, i); if (kType_Implements(type, kTypeOf(kCompressor))) { kCompressionType guid = kCast(kCompressorVTable*, kType_IVTable(type, kTypeOf(kCompressor)))->VCompressionType(); kTest(kMap_ReplaceT(sobj->guidToCompressor, &guid, &type)); } } } kFinally { kLock_Exit(sobj->lock); kEndFinally(); } return kOK; } kFx(kStatus) xkDat5Serializer_OnAssemblyUnload(kPointer receiver, kAssembly assembly, void* args) { kStaticObj(kDat5Serializer); kSize i, j; kCheck(kLock_Enter(sobj->lock)); kTry { //clear serialization guid-to-info map if (kObject_IsShared(sobj->guidToInfo)) { kMap clone = kNULL; kTest(kObject_Clone(&clone, sobj->guidToInfo, kNULL)); kTest(kObject_Destroy(sobj->guidToInfo)); sobj->guidToInfo = clone; } for (i = 0; i < kAssembly_TypeCount(assembly); ++i) { kType type = kAssembly_TypeAt(assembly, i); kMapItem item = kNULL; for (j = 0; j < kType_VersionCount(type); ++j) { kTypeVersion version = kType_VersionAt(type, j); if (kStrEquals(kType_VersionFormat(type, version), xkDAT5_FORMAT)) { k32u typeId, typeVersionId, typeGuid; if (sscanf(kType_VersionGuid(type, version), "%u-%u", &typeId, &typeVersionId) == 2) { typeGuid = xkDat5Guid_Create(typeId, typeVersionId); if (kSuccess(kMap_FindItemT(sobj->guidToInfo, &typeGuid, &item))) { kTest(kMap_RemoveItem(sobj->guidToInfo, item)); } } } } } //clear compression guid-to-type map if (kObject_IsShared(sobj->guidToCompressor)) { kMap clone = kNULL; kTest(kObject_Clone(&clone, sobj->guidToCompressor, kNULL)); kTest(kObject_Destroy(sobj->guidToCompressor)); sobj->guidToCompressor = clone; } for (i = 0; i < kAssembly_TypeCount(assembly); ++i) { kType type = kAssembly_TypeAt(assembly, i); if (kType_Implements(type, kTypeOf(kCompressor))) { kCompressionType guid = kCast(kCompressorVTable*, kType_IVTable(type, kTypeOf(kCompressor)))->VCompressionType(); kMapItem item = kNULL; if (kSuccess(kMap_FindItemT(sobj->guidToCompressor, &guid, &item))) { kTest(kMap_RemoveItem(sobj->guidToCompressor, item)); } } } } kFinally { kLock_Exit(sobj->lock); kEndFinally(); } return kOK; } kFx(kStatus) xkDat5Serializer_GetTypeLookups(kMap* guidToInfo, kMap* guidToCompressor) { kStaticObj(kDat5Serializer); kCheck(kLock_Enter(sobj->lock)); kTry { kTest(kObject_Share(sobj->guidToInfo)); *guidToInfo = sobj->guidToInfo; kTest(kObject_Share(sobj->guidToCompressor)); *guidToCompressor = sobj->guidToCompressor; } kFinally { kLock_Exit(sobj->lock); kEndFinally(); } return kOK; } kFx(kStatus) kDat5Serializer_Construct(kDat5Serializer* serializer, kStream stream, kAlloc allocator) { kAlloc alloc = kAlloc_Fallback(allocator); kStatus status; kCheck(kAlloc_GetObject(alloc, kTypeOf(kDat5Serializer), serializer)); if (!kSuccess(status = kDat5Serializer_VInit(*serializer, kTypeOf(kDat5Serializer), stream, alloc))) { kAlloc_FreeRef(alloc, serializer); } return status; } kFx(kStatus) kDat5Serializer_VInit(kDat5Serializer serializer, kType type, kStream stream, kAlloc allocator) { kObjR(kDat5Serializer, serializer); kStatus status; kCheck(kSerializer_VInit(serializer, type, stream, allocator)); obj->objectDepth = 0; obj->guidToInfo = kNULL; obj->guidToCompressor = kNULL; obj->typeToInfo = kNULL; obj->currentCompressionAlgorithm = kCOMPRESSION_TYPE_NULL; obj->currentCompressionPreset = 0; obj->compressStream = kNULL; obj->currentDecompressionAlgorithm = kCOMPRESSION_TYPE_NULL; obj->decompressStream = kNULL; kStrCopy(obj->base.format, kCountOf(obj->base.format), xkDAT5_FORMAT); kTry { kTest(xkDat5Serializer_GetTypeLookups(&obj->guidToInfo, &obj->guidToCompressor)); kTest(kMap_Construct(&obj->typeToInfo, kTypeOf(kType), kTypeOf(xkDat5SerializerTypeInfo), 64, allocator)); } kCatch(&status) { kDat5Serializer_VRelease(serializer); kEndCatch(status); } return kOK; } kFx(kStatus) kDat5Serializer_VRelease(kDat5Serializer serializer) { kObj(kDat5Serializer, serializer); kCheck(kDestroyRef(&obj->compressStream)); kCheck(kDestroyRef(&obj->decompressStream)); kCheck(kObject_Destroy(obj->guidToInfo)); kCheck(kObject_Destroy(obj->guidToCompressor)); kCheck(kObject_Destroy(obj->typeToInfo)); kCheck(kSerializer_VRelease(serializer)); return kOK; } kFx(kBool) xkDat5Serializer_VReset(kDat5Serializer serializer) { kObj(kDat5Serializer, serializer); kCheck(kMap_Clear(obj->typeToInfo)); obj->objectDepth = 0; kCheck(kDestroyRef(&obj->compressStream)); obj->currentCompressionAlgorithm = kCOMPRESSION_TYPE_NULL; obj->currentCompressionPreset = 0; kCheck(kDestroyRef(&obj->decompressStream)); obj->currentDecompressionAlgorithm = kCOMPRESSION_TYPE_NULL; kCheck(kSerializer_VReset(serializer)); return kOK; } kFx(kStatus) kDat5Serializer_WriteData(kDat5Serializer serializer, kObject object, const kChar* label) { kObj(kDat5Serializer, serializer); kText16 dummyLabel = { 0 }; kStream storedWriteStream = obj->base.writeStream; kBool isOutermost = obj->objectDepth == 0; kBool shouldCompress = isOutermost && (obj->currentCompressionAlgorithm != kCOMPRESSION_TYPE_NULL); kStatus exception; obj->objectDepth++; kTry { if (isOutermost) { if (shouldCompress) { kTest(kSerializer_WriteByte(serializer, xkDAT5_SERIALIZER_CMD_COMPRESSED_OBJECT)); kTest(xkDat5Serializer_BeginCompression(serializer)); } else { kTest(kSerializer_WriteByte(serializer, xkDAT5_SERIALIZER_CMD_OBJECT)); } } if (kIsNull(object)) { kTest(kSerializer_Write32s(serializer, xkDAT5_SERIALIZER_CMD_NULL)); kTest(kSerializer_Write32u(serializer, 0)); } else { kType type = kObject_Type(object); kTypeVersion version = kNULL; kTest(kSerializer_WriteType(serializer, type, &version)); kTest(kSerializer_InvokeObjectSerializer(serializer, type, version, object)); kTest(kSerializer_Write32u(serializer, 0)); //kData version if (!kIsNull(label)) { if (!kSuccess(kStrCopy(dummyLabel, sizeof(dummyLabel), label))) {/* don't care */} } kTest(kSerializer_WriteCharArray(serializer, dummyLabel, sizeof(dummyLabel))); } if (shouldCompress) { kTest(xkDat5Serializer_EndCompression(serializer)); } obj->objectDepth--; } kCatch(&exception) { //precaution, in case of errors in compression; ensure write stream is restored obj->base.writeStream = storedWriteStream; if (isOutermost) { kSerializer_Reset(serializer); } kEndCatch(exception); } if (isOutermost && (kArrayList_Count(obj->base.writeSections) == 0)) { kCheck(kSerializer_Flush(serializer)); } return kOK; } kFx(kStatus) kDat5Serializer_VWriteObject(kDat5Serializer serializer, kObject object) { return kDat5Serializer_WriteData(serializer, object, kNULL); } kFx(kStatus) kDat5Serializer_ReadData(kDat5Serializer serializer, kObject* object, kChar* label, kSize capacity, kAlloc allocator) { kObj(kDat5Serializer, serializer); kAlloc alloc = kAlloc_Fallback(allocator); kType type = kNULL; kTypeVersion version = kNULL; kByte objectCommand = 0; kObject output = kNULL; kText16 dummyLabel; k32u dataVersion; kStream storedReadStream = obj->base.readStream; kBool isOutermost = obj->objectDepth == 0; kStatus exception; obj->objectDepth++; kTry { obj->base.readAlloc = alloc; if (obj->objectDepth == 1) { kTest(kSerializer_ReadByte(serializer, &objectCommand)); if (objectCommand == xkDAT5_SERIALIZER_CMD_COMPRESSED_OBJECT) { kTest(xkDat5Serializer_BeginDecompression(serializer)); } else { kTestState(objectCommand == xkDAT5_SERIALIZER_CMD_OBJECT); } } kTest(kSerializer_ReadType(serializer, &type, &version)); if (kIsNull(type)) { *object = kNULL; } else { kTest(xkSerializer_DeserializeObject(serializer, type, version, &output, alloc)); kTest(kSerializer_Read32u(serializer, &dataVersion)); kTest(kSerializer_ReadCharArray(serializer, dummyLabel, kCountOf(dummyLabel))); if (!kIsNull(label) && capacity != 0) { kTest(kStrCopy(label, capacity, dummyLabel)); } } if (objectCommand == xkDAT5_SERIALIZER_CMD_COMPRESSED_OBJECT) { kTest(xkDat5Serializer_EndDecompression(serializer)); } *object = output; obj->objectDepth--; } kCatch(&exception) { //precaution, in case of errors in compression; ensure read stream is restored obj->base.readStream = storedReadStream; kDisposeRef(&output); if (isOutermost) { kSerializer_Reset(serializer); } kEndCatch(exception); } return kOK; } kFx(kStatus) kDat5Serializer_VReadObject(kDat5Serializer serializer, kObject* object, kAlloc allocator) { return kDat5Serializer_ReadData(serializer, object, kNULL, 0, allocator); } kFx(kStatus) xkDat5Serializer_VWriteType(kDat5Serializer serializer, kType type, kTypeVersion* version) { kObj(kDat5Serializer, serializer); xkDat5SerializerTypeInfo info; k32u typeId, typeVersionId; if (!kSuccess(kMap_FindT(obj->typeToInfo, &type, &info))) { kCheck(kSerializer_FindCompatibleVersion(serializer, type, version)); if (sscanf(kType_VersionGuid(type, *version), "%u-%u", &typeId, &typeVersionId) != 2) { return kERROR_FORMAT; } info.type = type; info.version = *version; info.id = xkDat5Guid_Create(typeId, typeVersionId); kCheck(kMap_AddT(obj->typeToInfo, &type, &info)); } else { typeId = xkDat5Guid_Type(info.id); typeVersionId = xkDat5Guid_Version(info.id); } kCheck(kSerializer_Write32u(serializer, typeId)); kCheck(kSerializer_Write32u(serializer, typeVersionId)); *version = info.version; return kOK; } kFx(kStatus) xkDat5Serializer_VReadType(kDat5Serializer serializer, kType* type, kTypeVersion* version) { kObj(kDat5Serializer, serializer); const xkDat5SerializerTypeInfo* info = kNULL; kMapItem item = kNULL; k32s typeId; k32s typeVersionId; k32u guid; kCheck(kSerializer_Read32s(serializer, &typeId)); kCheck(kSerializer_Read32s(serializer, &typeVersionId)); if (typeId == xkDAT5_SERIALIZER_CMD_NULL) { //null is treated as special case *type = kNULL; *version = kNULL; return kOK; } else if (typeId == xkDAT5_SERIALIZER_CMD_REF) { //references not supported return kERROR_FORMAT; } guid = xkDat5Guid_Create((k32u)typeId, (k32u)typeVersionId); kCheck(kMap_FindItemT(obj->guidToInfo, &guid, &item)); info = kMap_ValueT(obj->guidToInfo, item, xkDat5SerializerTypeInfo); *type = info->type; *version = info->version; return kOK; } kFx(kStatus) xkDat5Serializer_WriteTypeWithoutVersion(kDat5Serializer serializer, kType type, kTypeVersion* version) { kObj(kDat5Serializer, serializer); xkDat5SerializerTypeInfo info; k32u typeId, typeVersionId; if (!kSuccess(kMap_FindT(obj->typeToInfo, &type, &info))) { kCheck(kSerializer_FindCompatibleVersion(serializer, type, version)); if (sscanf(kType_VersionGuid(type, *version), "%u-%u", &typeId, &typeVersionId) != 2) { return kERROR_FORMAT; } info.type = type; info.version = *version; info.id = xkDat5Guid_Create(typeId, typeVersionId); kCheck(kMap_AddT(obj->typeToInfo, &type, &info)); } else { typeId = xkDat5Guid_Type(info.id); typeVersionId = xkDat5Guid_Version(info.id); } kCheck(kSerializer_Write32u(serializer, typeId)); *version = info.version; return kOK; } kFx(kStatus) xkDat5Serializer_ReadTypeExplicitVersion(kDat5Serializer serializer, k32u typeVersionId, kType* type, kTypeVersion* version) { kObj(kDat5Serializer, serializer); const xkDat5SerializerTypeInfo* info = kNULL; kMapItem item = kNULL; k32s typeId; k32u guid; kCheck(kSerializer_Read32s(serializer, &typeId)); if (typeId == xkDAT5_SERIALIZER_CMD_NULL) { //null is treated as special case *type = kNULL; *version = kNULL; return kOK; } else if (typeId == xkDAT5_SERIALIZER_CMD_REF) { //references not supported return kERROR_FORMAT; } guid = xkDat5Guid_Create((k32u)typeId, typeVersionId); kCheck(kMap_FindItemT(obj->guidToInfo, &guid, &item)); info = kMap_ValueT(obj->guidToInfo, item, xkDat5SerializerTypeInfo); *type = info->type; *version = info->version; return kOK; } kFx(kStatus) xkDat5Serializer_BeginCompression(kDat5Serializer serializer) { kObj(kDat5Serializer, serializer); kCheck(kSerializer_BeginWrite(serializer, kTypeOf(k16u), kTRUE)); kCheck(kSerializer_Write8u(serializer, (k8u)obj->currentCompressionAlgorithm)); kCheck(kSerializer_EndWrite(serializer)); kCheck(xkSerializer_FlushEx(serializer, kFALSE)); //replace underlying stream with compression stream obj->base.writeStream = obj->compressStream; return kOK; } kFx(kStatus) xkDat5Serializer_EndCompression(kDat5Serializer serializer) { kObj(kDat5Serializer, serializer); //flush serializer to compression stream, but don't flush the compression stream itself kCheck(xkSerializer_FlushEx(serializer, kFALSE)); //finish writing the current compression stream kCheck(kCompressor_FinishWrite(obj->compressStream)); //restore underlying stream obj->base.writeStream = obj->base.stream; return kOK; } kFx(kStatus) xkDat5Serializer_BeginDecompression(kDat5Serializer serializer) { kObj(kDat5Serializer, serializer); kCompressionType algorithm = kCOMPRESSION_TYPE_NULL; kType decompressorType = kNULL; k8u temp8u = 0; //read out decompression configuration kCheck(kSerializer_BeginRead(serializer, kTypeOf(k16u), kTRUE)); kCheck(kSerializer_Read8u(serializer, &temp8u)); kCheck(kSerializer_EndRead(serializer)); algorithm = temp8u; //change compressor, if necessary if (obj->currentDecompressionAlgorithm != algorithm) { if (algorithm != kCOMPRESSION_TYPE_NULL) { kCheck(kMap_FindT(obj->guidToCompressor, &algorithm, &decompressorType)); //destroy the previous decompressor, if a new & different decompressor has been specified if (!kIsNull(obj->decompressStream)) { if (!kObject_Is(obj->decompressStream, decompressorType)) { kDestroyRef(&obj->decompressStream); } } //construct a new decompressor, if needed if (kIsNull(obj->decompressStream)) { kCheck(kCast(kCompressorVTable*, kType_IVTable(decompressorType, kTypeOf(kCompressor)))->VConstruct(&obj->decompressStream, obj->base.stream, kFALSE, 0, kObject_Alloc(serializer))); } } } obj->currentDecompressionAlgorithm = algorithm; //replace underlying stream with compression stream obj->base.readStream = obj->decompressStream; return kOK; } kFx(kStatus) xkDat5Serializer_EndDecompression(kDat5Serializer serializer) { kObj(kDat5Serializer, serializer); //finish reading the current compression stream kCheck(kCompressor_FinishRead(obj->decompressStream)); //restore underlying stream obj->base.readStream = obj->base.stream; return kOK; } kFx(kStatus) kDat5Serializer_EnableCompression(kDat5Serializer serializer, kCompressionType algorithm, k32s level) { kObj(kDat5Serializer, serializer); kType compressorType = kNULL; //check args if (algorithm != kCOMPRESSION_TYPE_NULL) { kType compressorType = kNULL; kVersion requestedVersion, supportedVersion; //check that the requested compression algorithm is available kCheck(kMap_FindT(obj->guidToCompressor, &algorithm, &compressorType)); //check that the requested compression algorithm is supported in the platform version that we're aiming for compatibility with requestedVersion = kSerializer_FindRequestedMinVersion(serializer, compressorType); supportedVersion = kCast(kCompressorVTable*, kType_IVTable(compressorType, kTypeOf(kCompressor)))->VRequiredVersion(); kCheckTrue(requestedVersion >= supportedVersion, kERROR_VERSION); } //change compressor, if necessary if ((obj->currentCompressionAlgorithm != algorithm) || (obj->currentCompressionPreset != level)) { if (algorithm != kCOMPRESSION_TYPE_NULL) { kCheck(kMap_FindT(obj->guidToCompressor, &algorithm, &compressorType)); //destroy the previous compressor, if a new & different compressor has been specified if (!kIsNull(obj->compressStream)) { if (!kObject_Is(obj->compressStream, compressorType) || (level != obj->currentCompressionPreset)) { kDestroyRef(&obj->compressStream); } } //construct a new compressor, if needed if (kIsNull(obj->compressStream)) { kCheck(kCast(kCompressorVTable*, kType_IVTable(compressorType, kTypeOf(kCompressor)))->VConstruct(&obj->compressStream, obj->base.stream, kTRUE, level, kObject_Alloc(serializer))); } obj->currentCompressionPreset = level; } obj->currentCompressionAlgorithm = algorithm; } return kOK; } kFx(kStatus) kDat5Serializer_SaveCompressed(kObject object, const kChar* filePath, kCompressionType algorithm, k32s level) { kSerializer writer = kNULL; kFile stream = kNULL; kTry { kTest(kFile_Construct(&stream, filePath, kFILE_MODE_WRITE, kNULL)); kTest(kDat5Serializer_Construct(&writer, stream, kNULL)); kTest(kDat5Serializer_EnableCompression(writer, algorithm, level)); kTest(kSerializer_WriteObject(writer, object)); } kFinally { kCheck(kObject_Destroy(writer)); kCheck(kObject_Destroy(stream)); kEndFinally(); } return kOK; }