diff --git a/src/apps/haikudepot/Jamfile b/src/apps/haikudepot/Jamfile index e947bf98e0..6855ec2da1 100644 --- a/src/apps/haikudepot/Jamfile +++ b/src/apps/haikudepot/Jamfile @@ -3,56 +3,59 @@ SubDir HAIKU_TOP src apps haikudepot ; # disable -Werror for this directory only SubDirC++Flags -Wno-error ; -include [ FDirName $(SUBDIR) build jam HdsSchemaGenRules ] ; +include [ FDirName $(SUBDIR) build jam JsonSchemaGenRules ] ; UsePrivateHeaders interface net netservices package shared storage ; UsePrivateHeaders support locale ; HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY = $(TARGET_COMMON_DEBUG_LOCATE_TARGET) ; +# Generated sources JSON Schemas + +local genServerDumpExportPkgSchema = + [ FDirName $(SUBDIR) server schema dumpexportpkg.json ] ; +local genServerDumpExportRepositorySchema = + [ FDirName $(SUBDIR) server schema dumpexportrepository.json ] ; +local genServerDumpExportReferenceSchema = + [ FDirName $(SUBDIR) server schema dumpexportreference.json ] ; + # During the build process, some sources are generated into directories. These # are the directories. -local dumpExportRepositoryBulkListenerTargetDirectory = - [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) - dumpexportrepositorybulklistener ] ; -local dumpExportPkgBulkListenerTargetDirectory = - [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) - dumpexportpkgbulklistener ] ; -local dumpExportReferenceListenerTargetDirectory = - [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) - dumpexportreferencelistener ] ; +local genServerDumpExportRepositoryBulkListenerTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportrepositorybulklistener ] ; +local genServerDumpExportPkgBulkListenerTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportpkgbulklistener ] ; +local genServerDumpExportReferenceListenerTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportreferencelistener ] ; -local dumpExportPkgModelTargetDirectory = - [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) - dumpexportpkgmodel ] ; -local dumpExportRepositoryModelTargetDirectory = - [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) - dumpexportrepositorymodel ] ; -local dumpExportReferenceModelTargetDirectory = - [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) - dumpexportreferencemodel ] ; +local genServerDumpExportPkgModelTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportpkgmodel ] ; +local genServerDumpExportRepositoryModelTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportrepositorymodel ] ; +local genServerDumpExportReferenceModelTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportreferencemodel ] ; # During the build process, some sources are generated into a directory. In # order to maintain a timestamp on that generation process, a dummy file is # used to signify the target of the generation. The leafname of this dummy file # is defined here. -local dummyFile = "dummy.dat" ; +local genDummyFile = "dummy.dat" ; -local dumpExportRepositoryBulkListenerTargetFile = - [ FDirName $(dumpExportRepositoryBulkListenerTargetDirectory) $(dummyFile) ] ; -local dumpExportPkgBulkListenerTargetFile = - [ FDirName $(dumpExportPkgBulkListenerTargetDirectory) $(dummyFile) ] ; -local dumpExportReferenceListenerTargetFile = - [ FDirName $(dumpExportReferenceListenerTargetDirectory) $(dummyFile) ] ; +local genServerDumpExportRepositoryBulkListenerTargetFile = + [ FDirName $(genServerDumpExportRepositoryBulkListenerTargetDirectory) $(genDummyFile) ] ; +local genServerDumpExportPkgBulkListenerTargetFile = + [ FDirName $(genServerDumpExportPkgBulkListenerTargetDirectory) $(genDummyFile) ] ; +local genServerDumpExportReferenceListenerTargetFile = + [ FDirName $(genServerDumpExportReferenceListenerTargetDirectory) $(genDummyFile) ] ; -local dumpExportPkgModelTargetFile = - [ FDirName $(dumpExportPkgModelTargetDirectory) $(dummyFile) ] ; -local dumpExportRepositoryModelTargetFile = - [ FDirName $(dumpExportRepositoryModelTargetDirectory) $(dummyFile) ] ; -local dumpExportReferenceModelTargetFile = - [ FDirName $(dumpExportReferenceModelTargetDirectory) $(dummyFile) ] ; +local genServerDumpExportPkgModelTargetFile = + [ FDirName $(genServerDumpExportPkgModelTargetDirectory) $(genDummyFile) ] ; +local genServerDumpExportRepositoryModelTargetFile = + [ FDirName $(genServerDumpExportRepositoryModelTargetDirectory) $(genDummyFile) ] ; +local genServerDumpExportReferenceModelTargetFile = + [ FDirName $(genServerDumpExportReferenceModelTargetDirectory) $(genDummyFile) ] ; # source directories local sourceDirs = @@ -77,13 +80,13 @@ for sourceDir in $(sourceDirs) { SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src servers package ] ; -SEARCH_SOURCE += $(dumpExportRepositoryBulkListenerTargetDirectory) ; -SEARCH_SOURCE += $(dumpExportPkgBulkListenerTargetDirectory) ; -SEARCH_SOURCE += $(dumpExportReferenceListenerTargetDirectory) ; +SEARCH_SOURCE += $(genServerDumpExportRepositoryBulkListenerTargetDirectory) ; +SEARCH_SOURCE += $(genServerDumpExportPkgBulkListenerTargetDirectory) ; +SEARCH_SOURCE += $(genServerDumpExportReferenceListenerTargetDirectory) ; -SEARCH_SOURCE += $(dumpExportPkgModelTargetDirectory) ; -SEARCH_SOURCE += $(dumpExportRepositoryModelTargetDirectory) ; -SEARCH_SOURCE += $(dumpExportReferenceModelTargetDirectory) ; +SEARCH_SOURCE += $(genServerDumpExportPkgModelTargetDirectory) ; +SEARCH_SOURCE += $(genServerDumpExportRepositoryModelTargetDirectory) ; +SEARCH_SOURCE += $(genServerDumpExportReferenceModelTargetDirectory) ; local textDocumentSources = # edits_generic @@ -242,49 +245,30 @@ local applicationSources = ResultWindow.cpp ; -local generatedPkgModelSourceFiles = - DumpExportPkg.cpp - DumpExportPkgCategory.cpp - DumpExportPkgScreenshot.cpp - DumpExportPkgVersion.cpp -; +local genServerPkgModelSourceFiles = + DumpExportPkgModel.cpp ; +local genServerRepositoryModelSourceFiles = + DumpExportRepositoryModel.cpp ; +local genServerReferenceModelSourceFiles = + DumpExportReferenceModel.cpp ; -local generatedRepositoryModelSourceFiles = - DumpExportRepository.cpp - DumpExportRepositorySource.cpp - DumpExportRepositorySourceMirror.cpp -; - -local generatedReferenceModelSourceFiles = - DumpExportReference.cpp - DumpExportReferenceNaturalLanguage.cpp - DumpExportReferencePkgCategory.cpp - DumpExportReferenceUserRatingStability.cpp - DumpExportReferenceCountry.cpp -; - -local generatedPkgParserSourceFiles = - DumpExportPkgJsonListener.cpp -; - -local generatedRepositoryParserSourceFiles = - DumpExportRepositoryJsonListener.cpp -; - -local generatedReferenceParserSourceFiles = - DumpExportReferenceJsonListener.cpp -; +local genServerPkgParserSourceFiles = + DumpExportPkgJsonListener.cpp ; +local genServerRepositoryParserSourceFiles = + DumpExportRepositoryJsonListener.cpp ; +local genServerReferenceParserSourceFiles = + DumpExportReferenceJsonListener.cpp ; Application HaikuDepot : $(applicationSources) $(textDocumentSources) - $(generatedPkgModelSourceFiles) - $(generatedRepositoryModelSourceFiles) - $(generatedReferenceModelSourceFiles) - $(generatedPkgParserSourceFiles) - $(generatedRepositoryParserSourceFiles) - $(generatedReferenceParserSourceFiles) + $(genServerPkgModelSourceFiles) + $(genServerRepositoryModelSourceFiles) + $(genServerReferenceModelSourceFiles) + $(genServerPkgParserSourceFiles) + $(genServerRepositoryParserSourceFiles) + $(genServerReferenceParserSourceFiles) : be package bnetapi translation libnetservices.a libcolumnlistview.a shared [ TargetLibstdc++ ] [ TargetLibsupc++ ] localestub @@ -302,79 +286,81 @@ DoCatalogs HaikuDepot : # JSON schemas that are defined in the HaikuDepotServer system. See the # included Jam rules and actions for the definitions. -HdsSchemaGenModel $(dumpExportPkgModelTargetFile) - : dumpexportpkg.json : jsonschema2cppmodel.py ; +JsonSchemaGenModel $(genServerDumpExportPkgModelTargetFile) + : $(genServerDumpExportPkgSchema) ; +JsonSchemaGenModel $(genServerDumpExportRepositoryModelTargetFile) + : $(genServerDumpExportRepositorySchema) ; +JsonSchemaGenModel $(genServerDumpExportReferenceModelTargetFile) + : $(genServerDumpExportReferenceSchema) ; -HdsSchemaGenModel $(dumpExportRepositoryModelTargetFile) - : dumpexportrepository.json : jsonschema2cppmodel.py ; +JsonSchemaGenParser $(genServerDumpExportRepositoryBulkListenerTargetFile) + : $(genServerDumpExportRepositorySchema) ; +JsonSchemaGenParser $(genServerDumpExportPkgBulkListenerTargetFile) + : $(genServerDumpExportPkgSchema) ; +JsonSchemaGenParser $(genServerDumpExportReferenceListenerTargetFile) + : $(genServerDumpExportReferenceSchema) ; -HdsSchemaGenModel $(dumpExportReferenceModelTargetFile) - : dumpexportreference.json : jsonschema2cppmodel.py ; +JsonSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(genServerPkgParserSourceFiles) ] + [ FGristFiles $(genServerPkgParserSourceFiles:S=.h) ] + : $(genServerDumpExportPkgBulkListenerTargetDirectory) + : $(genServerDumpExportPkgBulkListenerTargetFile) +; -HdsSchemaGenBulkParser $(dumpExportRepositoryBulkListenerTargetFile) - : dumpexportrepository.json : jsonschema2cppparser.py ; +JsonSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(genServerRepositoryParserSourceFiles) ] + [ FGristFiles $(genServerRepositoryParserSourceFiles:S=.h) ] + : $(genServerDumpExportRepositoryBulkListenerTargetDirectory) + : $(genServerDumpExportRepositoryBulkListenerTargetFile) +; -HdsSchemaGenBulkParser $(dumpExportPkgBulkListenerTargetFile) - : dumpexportpkg.json : jsonschema2cppparser.py ; +JsonSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(genServerReferenceParserSourceFiles) ] + [ FGristFiles $(genServerReferenceParserSourceFiles:S=.h) ] + : $(genServerDumpExportReferenceListenerTargetDirectory) + : $(genServerDumpExportReferenceListenerTargetFile) +; -HdsSchemaGenParser $(dumpExportReferenceListenerTargetFile) - : dumpexportreference.json : jsonschema2cppparser.py ; +JsonSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(genServerRepositoryModelSourceFiles) ] + [ FGristFiles $(genServerRepositoryModelSourceFiles:S=.h) ] + : $(genServerDumpExportRepositoryModelTargetDirectory) + : $(genServerDumpExportRepositoryModelTargetFile) +; -HdsSchemaGenAppSrcDependsOnGeneration - [ FGristFiles $(generatedPkgParserSourceFiles) ] - [ FGristFiles $(generatedPkgParserSourceFiles:S=.h) ] - : $(dumpExportPkgBulkListenerTargetDirectory) - : $(dumpExportPkgBulkListenerTargetFile) ; +JsonSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(genServerPkgModelSourceFiles) ] + [ FGristFiles $(genServerPkgModelSourceFiles:S=.h) ] + : $(genServerDumpExportPkgModelTargetDirectory) + : $(genServerDumpExportPkgModelTargetFile) +; -HdsSchemaGenAppSrcDependsOnGeneration - [ FGristFiles $(generatedRepositoryParserSourceFiles) ] - [ FGristFiles $(generatedRepositoryParserSourceFiles:S=.h) ] - : $(dumpExportRepositoryBulkListenerTargetDirectory) - : $(dumpExportRepositoryBulkListenerTargetFile) ; - -HdsSchemaGenAppSrcDependsOnGeneration - [ FGristFiles $(generatedReferenceParserSourceFiles) ] - [ FGristFiles $(generatedReferenceParserSourceFiles:S=.h) ] - : $(dumpExportReferenceListenerTargetDirectory) - : $(dumpExportReferenceListenerTargetFile) ; - -HdsSchemaGenAppSrcDependsOnGeneration - [ FGristFiles $(generatedRepositoryModelSourceFiles) ] - [ FGristFiles $(generatedRepositoryModelSourceFiles:S=.h) ] - : $(dumpExportRepositoryModelTargetDirectory) - : $(dumpExportRepositoryModelTargetFile) ; - -HdsSchemaGenAppSrcDependsOnGeneration - [ FGristFiles $(generatedPkgModelSourceFiles) ] - [ FGristFiles $(generatedPkgModelSourceFiles:S=.h) ] - : $(dumpExportPkgModelTargetDirectory) - : $(dumpExportPkgModelTargetFile) ; - -HdsSchemaGenAppSrcDependsOnGeneration - [ FGristFiles $(generatedReferenceModelSourceFiles) ] - [ FGristFiles $(generatedReferenceModelSourceFiles:S=.h) ] - : $(dumpExportReferenceModelTargetDirectory) - : $(dumpExportReferenceModelTargetFile) ; +JsonSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(genServerReferenceModelSourceFiles) ] + [ FGristFiles $(genServerReferenceModelSourceFiles:S=.h) ] + : $(genServerDumpExportReferenceModelTargetDirectory) + : $(genServerDumpExportReferenceModelTargetFile) +; # This will ensure that if any of the generated files' header files change, then # the application should be re-built. Includes [ FGristFiles $(applicationSources) ] : - $(dumpExportRepositoryBulkListenerTargetDirectory) - $(dumpExportPkgBulkListenerTargetDirectory) - $(dumpExportReferenceListenerTargetDirectory) - $(dumpExportPkgModelTargetDirectory) - $(dumpExportRepositoryModelTargetDirectory) - $(dumpExportReferenceModelTargetDirectory) + $(genServerDumpExportRepositoryBulkListenerTargetDirectory) + $(genServerDumpExportPkgBulkListenerTargetDirectory) + $(genServerDumpExportReferenceListenerTargetDirectory) + $(genServerDumpExportPkgModelTargetDirectory) + $(genServerDumpExportRepositoryModelTargetDirectory) + $(genServerDumpExportReferenceModelTargetDirectory) ; Depends [ FGristFiles $(applicationSources:S=.o) ] : - [ FGristFiles $(generatedPkgParserSourceFiles:S=.h) ] - [ FGristFiles $(generatedPkgModelSourceFiles:S=.h) ] - [ FGristFiles $(generatedRepositoryParserSourceFiles:S=.h) ] - [ FGristFiles $(generatedRepositoryModelSourceFiles:S=.h) ] - [ FGristFiles $(generatedReferenceParserSourceFiles:S=.h) ] - [ FGristFiles $(generatedReferenceModelSourceFiles:S=.h) ] + [ FGristFiles $(genServerPkgParserSourceFiles:S=.h) ] + [ FGristFiles $(genServerPkgModelSourceFiles:S=.h) ] + [ FGristFiles $(genServerRepositoryParserSourceFiles:S=.h) ] + [ FGristFiles $(genServerRepositoryModelSourceFiles:S=.h) ] + [ FGristFiles $(genServerReferenceParserSourceFiles:S=.h) ] + [ FGristFiles $(genServerReferenceModelSourceFiles:S=.h) ] ; diff --git a/src/apps/haikudepot/build/jam/HdsSchemaGenRules b/src/apps/haikudepot/build/jam/HdsSchemaGenRules deleted file mode 100644 index fdc699a931..0000000000 --- a/src/apps/haikudepot/build/jam/HdsSchemaGenRules +++ /dev/null @@ -1,117 +0,0 @@ -# ===================================== -# Copyright 2019-2023, Andrew Lindesay -# Distributed under the terms of the MIT License. -# ===================================== - -# HaikuDepotServer has a number of data-transfer-objects (DTO) that are defined -# by JSON schemas. The server uses these schemas to produce the objects at -# compile time. Likewise, the schema files also generate C++ side DTO model -# objects in the form of .cpp and .h files as well. This way the -# HaikuDepotServer server and HaikuDepot desktop application are able to -# communicate more 'safely'. The schema files still need to be copied from -# the server source to the Haiku source, but the generation process will ensure -# that the data-structures are consistent. -# -# The C++ side classes are generated with python scripts that are included in -# the Haiku source. These rules and actions take care of making sure that the -# python scripts are run when necessary to generate the C++ side classes. Note -# that there are two sorts of classes generated here; the model DTO objects and -# also the supporting classes that parse the DTO objects. The parsing classes -# are intended to be used with Haiku JSON parsing systems. - -# pragma mark - Generic - -actions HdsSchemaGenTouch -{ - touch $(1) -} - -# pragma mark - Model Class Generation - -# 1 : the dummy file in the class generation directory (target) -# 2 : the JSON schema file -# 3 : the Python script to use - -rule HdsSchemaGenModel -{ - SEARCH on $(2) = [ FDirName $(SUBDIR) server schema ] ; - SEARCH on $(3) = [ FDirName $(SUBDIR) build scripts ] ; - - MkDir1 $(1:D) ; - CleanDir clean : $(1:D) ; - Depends $(1) : $(2) $(3) $(1:D) ; - HdsSchemaGenModel1 $(1) : $(2) $(3) $(1:D) ; -} - -actions HdsSchemaGenModel1 -{ - $(HOST_PYTHON) $(2[2]) -i $(2[1]) --outputdirectory $(2[3]) - touch $(1) -} - -# pragma mark - Bulk Parsing Class Generation - -# 1 : the dummy file in the class generation directory (target) -# 2 : the JSON schema file -# 3 : the Python script to use - -rule HdsSchemaGenBulkParser -{ - SEARCH on $(2) = [ FDirName $(SUBDIR) server schema ] ; - SEARCH on $(3) = [ FDirName $(SUBDIR) build scripts ] ; - - MkDir1 $(1:D) ; - CleanDir clean : $(1:D) ; - Depends $(1) : $(2) $(3) $(1:D) ; - HdsSchemaGenBulkParser1 $(1) : $(2) $(3) $(1:D) ; -} - -actions HdsSchemaGenBulkParser1 -{ - $(HOST_PYTHON) $(2[2]) -i $(2[1]) --outputdirectory $(2[3]) - touch $(1) -} - -# pragma mark - Non-Bulk Parsing Class Generation - -# 1 : the dummy file in the class generation directory (target) -# 2 : the JSON schema file -# 3 : the Python script to use - -rule HdsSchemaGenParser -{ - SEARCH on $(2) = [ FDirName $(SUBDIR) server schema ] ; - SEARCH on $(3) = [ FDirName $(SUBDIR) build scripts ] ; - - MkDir1 $(1:D) ; - CleanDir clean : $(1:D) ; - Depends $(1) : $(2) $(3) $(1:D) ; - HdsSchemaGenParser1 $(1) : $(2) $(3) $(1:D) ; -} - -actions HdsSchemaGenParser1 -{ - $(HOST_PYTHON) $(2[2]) -i $(2[1]) --outputdirectory $(2[3]) - touch $(1) -} - -# pragma mark - Registering Generated Classes - -# Because a number of .cpp and .h files will be generated from a single python -# script's run, it is necessary to introduce a dependency between the known -# output files and the target for a given python script run. - -# 1 : generated files (.h and .cpp) -# 2 : target directory generated files will be placed in -# 3 : target that will generate the generated files (dummy file) - -rule HdsSchemaGenAppSrcDependsOnGeneration { - MakeLocate $(1) : $(2) ; - MkDir1 $(2) ; - Depends $(1) : $(2) $(3) ; - Clean clean : $(1) ; - - # just in case the dummy file ends up being newer than the generated - # sources, update the modified timestamp on the generated files. - HdsSchemaGenTouch $(1) ; -} diff --git a/src/apps/haikudepot/build/jam/JsonSchemaGenRules b/src/apps/haikudepot/build/jam/JsonSchemaGenRules new file mode 100644 index 0000000000..db5c00d5a8 --- /dev/null +++ b/src/apps/haikudepot/build/jam/JsonSchemaGenRules @@ -0,0 +1,125 @@ +# ===================================== +# Copyright 2019-2026, Andrew Lindesay +# Distributed under the terms of the MIT License. +# ===================================== + +# HaikuDepotServer has a number of data-transfer-objects (DTO) that are defined +# by JSON schemas. The server uses these schemas to produce the objects at +# compile time. Likewise, the schema files also generate C++ side DTO model +# objects in the form of .cpp and .h files as well. This way the +# HaikuDepotServer server and HaikuDepot desktop application are able to +# communicate more 'safely'. The schema files still need to be copied from +# the server source to the Haiku source, but the generation process will ensure +# that the data-structures are consistent. +# +# The C++ side classes are generated with python scripts that are included in +# the Haiku source. These rules and actions take care of making sure that the +# python scripts are run when necessary to generate the C++ side classes. Note +# that there are two sorts of classes generated here; the model DTO objects and +# also the supporting classes that parse the DTO objects. The parsing classes +# are intended to be used with Haiku JSON parsing systems. + +# ===================================== +# pragma mark - Generic + +actions JsonSchemaGenTouch +{ + touch $(1) +} + +# Because a number of .cpp and .h files will be generated from a single python +# script's run, it is necessary to introduce a dependency between the known +# output files and the target for a given python script run. + +# 1 : generated files (.h and .cpp) +# 2 : target directory generated files will be placed in +# 3 : target that will generate the generated files (dummy file) + +rule JsonSchemaGenAppSrcDependsOnGeneration { + local generatedFiles = $(1) ; + local targetDirectory = $(2) ; + local dummy = $(3) ; + + MakeLocate $(generatedFiles) : $(targetDirectory) ; + MkDir1 $(targetDirectory) ; + Depends $(generatedFiles) : $(targetDirectory) $(dummy) ; + Clean clean : $(generatedFiles) ; + + # just in case the dummy file ends up being newer than the generated + # sources, update the modified timestamp on the generated files. + JsonSchemaGenTouch $(generatedFiles) ; +} + +# ===================================== +# pragma mark - Model Class Generation + +# This section is responsible for generation of .h and .cpp files which +# represent the data model described in the supplied JSON schema file. These +# files do not include parsers. +# +# A dummy file is used as a dependency item which then has the two generated +# file as it's dependency. This approach avoids complications around the +# generated target being two files rather than one. + +# 1 : the dummy file in the class generation directory (target) +# 2 : the JSON schema file + +rule JsonSchemaGenModel +{ + local dummy = $(1) ; + local schema = $(2) ; + + local script = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts jsonschema2cppmodel.py ] ; + local commonScript = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts jsonschemacommon.py ] ; + local headerTemplate = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts template modelheader.mustache ] ; + local implementationTemplate = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts template modelimplementation.mustache ] ; + + MkDir1 $(dummy:D) ; + CleanDir clean : $(dummy:D) ; + Depends $(dummy) : $(schema) $(script) $(commonScript) $(headerTemplate) $(implementationTemplate) $(dummy:D) ; + JsonSchemaGenModel1 $(dummy) : $(schema) $(script) $(dummy:D) ; +} + +actions JsonSchemaGenModel1 +{ + $(HOST_PYTHON) $(2[2]) --jsonschemafile $(2[1]) --outputdirectory $(2[3]) + touch $(1) +} + +# ===================================== +# pragma mark - Parsing Class Generation + +# This section is responsible for generation of .h and .cpp files which +# represent a listener that is intended for use with the `BJson` class to +# parse JSON data according to a supplied JSON Schema model. The C++ +# representation of the schema as a model must be also generated with +# `JsonSchemaGenModel`. +# +# A dummy file is used as a dependency item which then has the two generated +# file as it's dependency. This approach avoids complications around the +# generated target being two files rather than one. + +# 1 : the dummy file in the class generation directory (target) +# 2 : the JSON schema file + +rule JsonSchemaGenParser +{ + local dummy = $(1) ; + local schema = $(2) ; + + local script = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts jsonschema2cppparser.py ] ; + local commonScript = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts jsonschemacommon.py ] ; + local headerTemplate = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts template modelheader.mustache ] ; + local implementationTemplate = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts template modelimplementation.mustache ] ; + + MkDir1 $(dummy:D) ; + CleanDir clean : $(dummy:D) ; + Depends $(dummy) : $(schema) $(script) $(commonScript) $(headerTemplate) $(implementationTemplate) $(dummy:D) ; + JsonSchemaGenParser1 $(dummy) : $(schema) $(script) $(dummy:D) ; +} + +actions JsonSchemaGenParser1 +{ + $(HOST_PYTHON) $(2[2]) --jsonschemafile $(2[1]) --outputdirectory $(2[3]) + touch $(1) +} \ No newline at end of file diff --git a/src/apps/haikudepot/build/scripts/hdsjsonschemacommon.py b/src/apps/haikudepot/build/scripts/hdsjsonschemacommon.py deleted file mode 100644 index 646b462563..0000000000 --- a/src/apps/haikudepot/build/scripts/hdsjsonschemacommon.py +++ /dev/null @@ -1,267 +0,0 @@ - -# ===================================== -# Copyright 2017-2023, Andrew Lindesay -# Distributed under the terms of the MIT License. -# ===================================== - -# common material related to generation of schema-generated artifacts. - -import datetime - - -# The possible JSON types -JSON_TYPE_STRING = "string" -JSON_TYPE_OBJECT = "object" -JSON_TYPE_ARRAY = "array" -JSON_TYPE_BOOLEAN = "boolean" -JSON_TYPE_INTEGER = "integer" -JSON_TYPE_NUMBER = "number" - - -# The possible C++ types -CPP_TYPE_STRING = "BString" -CPP_TYPE_ARRAY = "BObjectList" -CPP_TYPE_BOOLEAN = "bool" -CPP_TYPE_INTEGER = "int64" -CPP_TYPE_NUMBER = "double" - - -# The possible C++ default values -CPP_DEFAULT_STRING = "NULL" -CPP_DEFAULT_OBJECT = "NULL" -CPP_DEFAULT_ARRAY = "NULL" -CPP_DEFAULT_BOOLEAN = "false" -CPP_DEFAULT_INTEGER = "0" -CPP_DEFAULT_NUMBER = "0.0" - - -def uniondicts(d1, d2): - d = dict(d1) - d.update(d2) - return d - - -def javatypetocppname(javaname): - return javaname[javaname.rindex('.')+1:] - - -def propnametocppname(propname): - return propname[0:1].upper() + propname[1:] - - -def propnametocppmembername(propname): - return "f" + propnametocppname(propname) - -def propmetadatatocppdefaultvalue(propmetadata): - type = propmetadata['type'] - - if type == JSON_TYPE_STRING: - return CPP_DEFAULT_STRING - if type == JSON_TYPE_BOOLEAN: - return CPP_DEFAULT_BOOLEAN - if type == JSON_TYPE_INTEGER: - return CPP_DEFAULT_INTEGER - if type == JSON_TYPE_NUMBER: - return CPP_DEFAULT_NUMBER - if type == JSON_TYPE_OBJECT: - return CPP_DEFAULT_OBJECT - if type == JSON_TYPE_ARRAY: - return CPP_DEFAULT_ARRAY - - raise Exception('unknown json-schema type [' + type + ']') - -def propmetadatatocpptypename(propmetadata): - type = propmetadata['type'] - - if type == JSON_TYPE_STRING: - return CPP_TYPE_STRING - if type == JSON_TYPE_BOOLEAN: - return CPP_TYPE_BOOLEAN - if type == JSON_TYPE_INTEGER: - return CPP_TYPE_INTEGER - if type == JSON_TYPE_NUMBER: - return CPP_TYPE_NUMBER - if type == JSON_TYPE_OBJECT: - javatype = propmetadata['javaType'] - - if not javatype or 0 == len(javatype): - raise Exception('missing "javaType" field') - - return javatypetocppname(javatype) - - if type == JSON_TYPE_ARRAY: - itemsmetadata = propmetadata['items'] - itemstype = itemsmetadata['type'] - - if not itemstype or 0 == len(itemstype): - raise Exception('missing "type" field') - - if itemstype == JSON_TYPE_OBJECT: - itemsjavatype = itemsmetadata['javaType'] - if not itemsjavatype or 0 == len(itemsjavatype): - raise Exception('missing "javaType" field') - return "%s<%s>" % (CPP_TYPE_ARRAY, javatypetocppname(itemsjavatype)) - - if itemstype == JSON_TYPE_STRING: - return "%s<%s>" % (CPP_TYPE_ARRAY, CPP_TYPE_STRING) - - raise Exception('unsupported type [%s]' % itemstype) - - raise Exception('unknown json-schema type [' + type + ']') - - -def propmetadatatypeisscalar(propmetadata): - type = propmetadata['type'] - return type == JSON_TYPE_BOOLEAN or type == JSON_TYPE_INTEGER or type == JSON_TYPE_NUMBER - - -def writetopcomment(f, inputfilename, variant): - f.write(( - '/*\n' - ' * Generated %s Object\n' - ' * source json-schema : %s\n' - ' * generated at : %s\n' - ' */\n' - ) % (variant, inputfilename, datetime.datetime.now().isoformat())) - -def collect_all_objects(schema: dict[str, any]) -> list[dict[str, any]]: - assembly = dict[str, dict[str, any]]() - - def accumulate_all_objects(obj: dict[str, any]) -> None: - assembly[obj["cppname"]] = obj - - for prop_name, prop in obj['properties'].items(): - if JSON_TYPE_ARRAY == prop["type"]: - array_items = prop['items'] - - if JSON_TYPE_OBJECT == array_items["type"] and not array_items["cppname"] in assembly: - accumulate_all_objects(array_items) - - if JSON_TYPE_OBJECT == prop["type"] and not prop["cppname"] in assembly: - accumulate_all_objects(prop) - - accumulate_all_objects(schema) - result = list(assembly.values()) - result.sort(key= lambda v: v["cppname"]) - - return result - - -def augment_schema(schema: dict[str, any]) -> None: - """This function will take the data from the JSON schema and will overlay any - specific information required for rendering the templates. - """ - - def derive_cpp_classname(obj: dict[str, any]) -> str: - obj_type = obj['type'] - - if obj_type != JSON_TYPE_OBJECT: - raise Exception('expecting object, but was [' + obj_type + ']') - - java_type = obj['javaType'] - - return javatypetocppname(java_type) - - def augment_property(prop: dict[str, any]) -> None: - prop_type = prop['type'] - - prop["cpptype"] = propmetadatatocpptypename(prop) - - is_scalar_type = propmetadatatypeisscalar(prop) - is_array_type = (prop_type == JSON_TYPE_ARRAY) - - prop["iscppscalartype"] = is_scalar_type - prop["isarray"] = is_array_type - prop["isstring"] = JSON_TYPE_STRING == prop_type - prop["isboolean"] = JSON_TYPE_BOOLEAN == prop_type - prop["isnumber"] = JSON_TYPE_NUMBER == prop_type - prop["isinteger"] = JSON_TYPE_INTEGER == prop_type - prop["isobject"] = JSON_TYPE_OBJECT == prop_type - prop["iscppnonscalarnoncollectiontype"] = not is_scalar_type and not is_array_type - prop["toplevelcppname"] = top_level_cpp_name - prop["cppdefaultvalue"] = propmetadatatocppdefaultvalue(prop) - - if not prop_type or 0 == len(prop_type): - raise Exception('missing "type" field') - - if JSON_TYPE_ARRAY == prop_type: - array_items = prop['items'] - array_items_type = array_items["type"] - - if array_items_type == JSON_TYPE_OBJECT: - augment_object(array_items) - else: - augment_property(array_items) - - if JSON_TYPE_OBJECT == prop_type: - augment_object(prop) - - def augment_object(obj: dict[str, any]) -> None: - - def collect_referenced_class_names() -> list[str]: - result = set() - properties = obj['properties'].items() - - if len(properties) > 15: - raise RuntimeError("an object has more than 15 properties" - " which is not allowed") - - for _, prop in obj['properties'].items(): - if prop['type'] == JSON_TYPE_ARRAY: - array_items = prop['items'] - if array_items['type'] == JSON_TYPE_OBJECT: - result.add(array_items['cppname']) - if prop['type'] == JSON_TYPE_OBJECT: - result.add(prop['cppname']) - - result_ordered = list(result) - result_ordered.sort() - return result_ordered - - def has_any_list_properties() -> bool: - for _, prop in obj['properties'].items(): - if prop['type'] == JSON_TYPE_ARRAY: - return True - return False - - obj_cpp_classname = derive_cpp_classname(obj) - obj["cppname"] = obj_cpp_classname - obj["cppnameupper"] = obj_cpp_classname.upper() - obj["cpptype"] = obj_cpp_classname - obj["hasanylistproperties"] = has_any_list_properties() - - # allows the object to know the top level object that contains it - obj["toplevelcppname"] = top_level_cpp_name - - properties = obj['properties'].items() - - for prop_name, prop in properties: - prop["cppname"] = propnametocppname(prop_name) - prop["cppmembername"] = propnametocppmembername(prop_name) - augment_property(prop) - - # mustache is not able to iterate over a dictionary; it can only - # iterate over a list where each item in the list has some properties. - - property_array = sorted( - [{ - "name": k, - "property": v, - "cppobjectname": obj_cpp_classname - # ^ this is the name of the containing object - } for k,v in properties], - key= lambda item: item["name"] - ) - - for i in range(len(property_array)): - property_array[i]["cppbitmaskexpression"] = "(1 << %d)" % i - - if 0 != len(property_array): - property_array[0]["isfirst"] = True - property_array[len(property_array) - 1]["islast"] = True - - obj["propertyarray"] = property_array - obj["referencedclasscpptypes"] = collect_referenced_class_names() - - top_level_cpp_name = derive_cpp_classname(schema) - augment_object(schema) \ No newline at end of file diff --git a/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py b/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py index a15ec5223d..8c31d6c4ec 100644 --- a/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py +++ b/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py @@ -1,266 +1,76 @@ #!/usr/bin/python # ===================================== -# Copyright 2017-2025, Andrew Lindesay +# Copyright 2017-2026, Andrew Lindesay # Distributed under the terms of the MIT License. # ===================================== -# This simple tool will read a JSON schema and will then generate -# some model objects that can be used to hold the data-structure -# in the C++ environment. +"""\ +This is a command line tool which is able to take a JSON Schema file as input +and will generate the `.h` and `.cpp` C++ source files for model classes that +represent the schema. The classes will appear in the output file in the correct +order according to their relationship in the schema. +""" -import json import argparse -import os -import hdsjsonschemacommon +import json +import pathlib +from pathlib import Path + +import jsonschemacommon import ustache -HEADER_TEMPLATE = """ -/* - * Generated Model Object for {{cppname}} - */ - -#ifndef GEN_JSON_SCHEMA_MODEL__{{cppnameupper}}_H -#define GEN_JSON_SCHEMA_MODEL__{{cppnameupper}}_H - -#include -#include - -{{#referencedclasscpptypes}}#include "{{.}}.h" -{{/referencedclasscpptypes}} - - -class {{cppname}} { -public: - {{cppname}}(); - virtual ~{{cppname}}(); - - void Reset(); -{{#propertyarray}}{{#property.iscppscalartype}} - {{property.cpptype}} {{property.cppname}}() const; - void Set{{property.cppname}}({{property.cpptype}} value); - void Set{{property.cppname}}Null(); - bool {{property.cppname}}IsNull() const; -{{/property.iscppscalartype}}{{#property.isarray}} - void AddTo{{property.cppname}}({{property.items.cpptype}}* value); - void Set{{property.cppname}}({{property.cpptype}}* value); - int32 Count{{property.cppname}}() const; - {{property.items.cpptype}}* {{property.cppname}}ItemAt(int32 index) const; - bool {{property.cppname}}IsNull() const; -{{/property.isarray}}{{#property.iscppnonscalarnoncollectiontype}} - {{property.cpptype}}* {{property.cppname}}() const; - void Set{{property.cppname}}({{property.cpptype}}* value); - void Set{{property.cppname}}Null(); - bool {{property.cppname}}IsNull() const; -{{/property.iscppnonscalarnoncollectiontype}} -{{/propertyarray}} - -public: -{{#propertyarray}} static const uint16 k{{property.cppname}}Bitmask; -{{/propertyarray}} - -private: - uint16 fHasValueBitmask; -{{#propertyarray}} {{property.cpptype}}{{^property.iscppscalartype}}*{{/property.iscppscalartype}} {{property.cppmembername}}; -{{/propertyarray}} -}; - -#endif // GEN_JSON_SCHEMA_MODEL__{{cppnameupper}}_H -""" - -IMPLEMENTATION_TEMPLATE = """ -/* - * Generated Model Object for {{cppname}} - */ - -#include "{{cppname}}.h" - - -{{#propertyarray}}/*static*/ const uint16 {{cppname}}::k{{property.cppname}}Bitmask = {{cppbitmaskexpression}}; -{{/propertyarray}} - - -{{cppname}}::{{cppname}}() - : - fHasValueBitmask(0), -{{#propertyarray}} {{property.cppmembername}}({{property.cppdefaultvalue}}){{^islast}},{{/islast}} -{{/propertyarray}}{ -} - - -{{cppname}}::~{{cppname}}() -{ - Reset(); -} - - -void -{{cppname}}::Reset() -{ -{{#propertyarray}}{{#property.isstring}} delete {{property.cppmembername}}; -{{/property.isstring}}{{#property.isobject}} delete {{property.cppmembername}}; -{{/property.isobject}}{{#property.isarray}} - if ({{property.cppmembername}} != NULL) { - for (int i = {{property.cppmembername}}->CountItems() - 1; i >= 0; i--) - delete {{property.cppmembername}}->ItemAt(i); - delete {{property.cppmembername}}; - } -{{/property.isarray}} {{property.cppmembername}} = {{property.cppdefaultvalue}}; -{{/propertyarray}} - fHasValueBitmask = 0; -} - -{{#propertyarray}}{{#property.iscppscalartype}}{{property.cpptype}} -{{cppobjectname}}::{{property.cppname}}() const -{ - return {{property.cppmembername}}; -} - - -void -{{cppobjectname}}::Set{{property.cppname}}({{property.cpptype}} value) -{ - fHasValueBitmask |= k{{property.cppname}}Bitmask; - {{property.cppmembername}} = value; -} - - -void -{{cppobjectname}}::Set{{property.cppname}}Null() -{ - fHasValueBitmask &= ~k{{property.cppname}}Bitmask; - {{property.cppmembername}} = {{property.cppdefaultvalue}}; -} - - -bool -{{cppobjectname}}::{{property.cppname}}IsNull() const -{ - return 0 == (fHasValueBitmask & k{{property.cppname}}Bitmask); -} - -{{/property.iscppscalartype}}{{#property.isarray}}void -{{cppobjectname}}::AddTo{{property.cppname}}({{property.items.cpptype}}* value) -{ - if ({{property.cppmembername}} == NULL) - {{property.cppmembername}} = new {{property.cpptype}}(); - {{property.cppmembername}}->AddItem(value); -} - - -void -{{cppobjectname}}::Set{{property.cppname}}({{property.cpptype}}* value) -{ - if ({{property.cppmembername}} != NULL) { - delete {{property.cppmembername}}; - } - {{property.cppmembername}} = value; -} - - -int32 -{{cppobjectname}}::Count{{property.cppname}}() const -{ - if ({{property.cppmembername}} == NULL) - return 0; - return {{property.cppmembername}}->CountItems(); -} - - -{{property.items.cpptype}}* -{{cppobjectname}}::{{property.cppname}}ItemAt(int32 index) const -{ - return {{property.cppmembername}}->ItemAt(index); -} - - -bool -{{cppobjectname}}::{{property.cppname}}IsNull() const -{ - return {{property.cppmembername}} == NULL; -} - -{{/property.isarray}}{{#property.iscppnonscalarnoncollectiontype}}{{property.cpptype}}* -{{cppobjectname}}::{{property.cppname}}() const -{ - return {{property.cppmembername}}; -} - - -void -{{cppobjectname}}::Set{{property.cppname}}({{property.cpptype}}* value) -{ - {{property.cppmembername}} = value; -} - - -void -{{cppobjectname}}::Set{{property.cppname}}Null() -{ - if (!{{property.cppname}}IsNull()) { - delete {{property.cppmembername}}; - {{property.cppmembername}} = NULL; - } -} - - -bool -{{cppobjectname}}::{{property.cppname}}IsNull() const -{ - return {{property.cppmembername}} == NULL; -} - -{{/property.iscppnonscalarnoncollectiontype}} -{{/propertyarray}} -""" - - -def write_models_for_schema(schema: dict[str, any], output_directory: str) -> None: - - def write_model_object(obj: dict[str, any]) -> None: - cpp_name = obj["cppname"] - cpp_header_filename = os.path.join(output_directory, cpp_name + '.h') - cpp_implementation_filename = os.path.join(output_directory, cpp_name + '.cpp') - - with open(cpp_header_filename, 'w') as cpp_h_file: - cpp_h_file.write(ustache.render( - HEADER_TEMPLATE, - obj, - escape= lambda x: x)) - - with open(cpp_implementation_filename, 'w') as cpp_i_file: - cpp_i_file.write(ustache.render( - IMPLEMENTATION_TEMPLATE, - obj, - escape= lambda x: x)) - - for obj in hdsjsonschemacommon.collect_all_objects(schema): - write_model_object(obj) - - def main(): parser = argparse.ArgumentParser( - description='Convert JSON schema to Haiku C++ Models') + description="Convert JSON schema to Haiku C++ Models") + parser.add_argument( - '-i', '--inputfile', + "--jsonschemafile", + dest = "jsonschemafile", required=True, - help='The input filename containing the JSON schema') + help="The input filename containing the JSON schema") + parser.add_argument( - '--outputdirectory', - help='The output directory where the C++ files should be written') + "--outputdirectory", + dest = "outputdirectory", + required=True, + help="The output filename where the C++ header and implementation file should be written") args = parser.parse_args() + schema_path = Path(args.jsonschemafile).absolute() - output_directory = args.outputdirectory + if not schema_path.is_file(): + raise RuntimeError(f"The input file [{schema_path}] does not exist") - if not output_directory: - output_directory = '.' + output_directory_path = Path(args.outputdirectory).absolute() + output_directory_path.mkdir(parents=True, exist_ok=True) - with open(args.inputfile) as inputfile: - schema = json.load(inputfile) - hdsjsonschemacommon.augment_schema(schema) - write_models_for_schema(schema, output_directory) + with open(schema_path) as input_file: + schema = json.load(input_file) + + jsonschemacommon.augment_schema(schema) + object_schemas = jsonschemacommon.collect_all_objects(schema) + + def output_to_path(output_path: pathlib.Path): + template_type = jsonschemacommon.path_to_extension_type(output_path) + template = jsonschemacommon.load_template("model", template_type) + template_model = { + "objects": object_schemas, + "outputfilename": output_path.name, + "outputheaderfile": output_path.stem + ".h", + "outputfilestemupper": output_path.stem.upper() + } + + with open(output_path, 'w') as out_file: + out_file.write(ustache.render( + template, + template_model, + escape= lambda x: x)) + + cpp_name = schema["cppname"] + output_to_path(output_directory_path / f"{cpp_name}Model.h") + output_to_path(output_directory_path / f"{cpp_name}Model.cpp") if __name__ == "__main__": diff --git a/src/apps/haikudepot/build/scripts/jsonschema2cppparser.py b/src/apps/haikudepot/build/scripts/jsonschema2cppparser.py index 1b53f531dc..fb421784be 100644 --- a/src/apps/haikudepot/build/scripts/jsonschema2cppparser.py +++ b/src/apps/haikudepot/build/scripts/jsonschema2cppparser.py @@ -1,944 +1,89 @@ #!/usr/bin/python # ===================================== -# Copyright 2017-2023, Andrew Lindesay +# Copyright 2017-2026, Andrew Lindesay # Distributed under the terms of the MIT License. # ===================================== -# This simple tool will read a JSON schema and will then generate a -# listener for use the 'BJson' class in the Haiku system. This -# allows data conforming to the schema to be able to be parsed. +"""\ +This is a command line tool which is able to take a JSON Schema file as input +and will generate the `.h` and `.cpp` C++ source files for producing a +"listener" class that is intended for use with the `BJson` class in the Haiku +libraries. The end result is that it is possible to parse JSON into the model +classes that would be generated by the `jsonschema2cppmodel.py` script. + +The generated logic will produce a "single" listener that will fully parse and +hold the output class. It will also produce a "bulk" listener that expects a +JSON structure such as `{ "items": [ .... ] }` where each item is an atomic +model to be parsed. + +In the case of the bulk listener, a handler also needs to be provided and the +handler will be invoked each time an item is processed. This is more of a +stream-like parsing approach and can reduce the volume of overall memory +necessary for parsing a large JSON payload. +""" -import string -import json import argparse -import os +import json +import pathlib +from pathlib import Path + +import jsonschemacommon import ustache -import hdsjsonschemacommon - -HEADER_TEMPLATE = """ -/* - * Generated Listener Object for {{toplevel.cppname}} - */ - -#ifndef GEN_JSON_SCHEMA_PARSER__{{toplevel.cppnameupper}}_H -#define GEN_JSON_SCHEMA_PARSER__{{toplevel.cppnameupper}}_H - -#include -#include - -#include "{{toplevel.cppname}}.h" - -class AbstractStacked{{toplevel.cppname}}JsonListener; - -class AbstractMain{{toplevel.cppname}}JsonListener : public BJsonEventListener { -friend class AbstractStacked{{toplevel.cppname}}JsonListener; -public: - AbstractMain{{toplevel.cppname}}JsonListener(); - virtual ~AbstractMain{{toplevel.cppname}}JsonListener(); - - void HandleError(status_t status, int32 line, const char* message); - void Complete(); - status_t ErrorStatus(); - -protected: - void SetStackedListener( - AbstractStacked{{toplevel.cppname}}JsonListener* listener); - status_t fErrorStatus; - AbstractStacked{{toplevel.cppname}}JsonListener* fStackedListener; -}; - -/*! Use this listener when you want to parse some JSON data that contains - just a single instance of {{toplevel.cppname}}. -*/ -class Single{{toplevel.cppname}}JsonListener - : public AbstractMain{{toplevel.cppname}}JsonListener { -friend class AbstractStacked{{toplevel.cppname}}JsonListener; -public: - Single{{toplevel.cppname}}JsonListener(); - virtual ~Single{{toplevel.cppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); - {{toplevel.cppname}}* Target(); - -private: - {{toplevel.cppname}}* fTarget; -}; - -/*! Concrete sub-classes of this class are able to respond to each - {{toplevel.cppname}}* instance as - it is parsed from the bulk container. When the stream is - finished, the Complete() method is invoked. - - Note that the item object will be deleted after the Handle method - is invoked. The Handle method need not take responsibility - for deleting the item itself. -*/ -class {{toplevel.cppname}}Listener { -public: - virtual bool Handle({{toplevel.cppname}}* item) = 0; - virtual void Complete() = 0; -}; - -/*! Use this listener, together with an instance of a concrete - subclass of {{toplevel.cppname}}Listener - in order to parse the JSON data in a specific "bulk - container" format. Each time that an instance of - {{toplevel.cppname}} - is parsed, the instance item listener will be invoked. -*/ -class BulkContainer{{toplevel.cppname}}JsonListener - : public AbstractMain{{toplevel.cppname}}JsonListener { -friend class AbstractStacked{{toplevel.cppname}}JsonListener; -public: - BulkContainer{{toplevel.cppname}}JsonListener( - {{toplevel.cppname}}Listener* itemListener); - ~BulkContainer{{toplevel.cppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); - -private: - {{toplevel.cppname}}Listener* fItemListener; -}; - -#endif // GEN_JSON_SCHEMA_PARSER__{{toplevel.cppnameupper}}_H -""" - -IMPLEMENTATION_TEMPLATE = """ -/* - * Generated Listener Object for {{toplevel.cppname}} - */ - -#include "{{toplevel.cppname}}JsonListener.h" -#include - -#include - -// #pragma mark - private interfaces for the stacked listeners - - -/*! This class is the top level of the stacked listeners. The stack structure - is maintained in a linked list and sub-classes implement specific behaviors - depending where in the parse tree the stacked listener is working at. -*/ -class AbstractStacked{{toplevel.cppname}}JsonListener : public BJsonEventListener { -public: - AbstractStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent); - ~AbstractStacked{{toplevel.cppname}}JsonListener(); - - void HandleError(status_t status, int32 line, const char* message); - void Complete(); - - status_t ErrorStatus(); - - AbstractStacked{{toplevel.cppname}}JsonListener* Parent(); - - virtual bool WillPop(); - -protected: - AbstractMain{{toplevel.cppname}}JsonListener* fMainListener; - - bool Pop(); - void Push(AbstractStacked{{toplevel.cppname}}JsonListener* stackedListener); - - -private: - AbstractStacked{{toplevel.cppname}}JsonListener* fParent; -}; - -class GeneralArrayStacked{{toplevel.cppname}}JsonListener : public AbstractStacked{{toplevel.cppname}}JsonListener { -public: - GeneralArrayStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent); - ~GeneralArrayStacked{{toplevel.cppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); -}; - -class GeneralObjectStacked{{toplevel.cppname}}JsonListener : public AbstractStacked{{toplevel.cppname}}JsonListener { -public: - GeneralObjectStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent); - ~GeneralObjectStacked{{toplevel.cppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); -}; - - -/*! Sometimes attributes of objects are able to be arrays of strings. This - listener will parse and return the array of strings. -*/ - -class StringList{{toplevel.cppname}}JsonListener : public AbstractStacked{{toplevel.cppname}}JsonListener { -public: - StringList{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent); - ~StringList{{toplevel.cppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); - BObjectList* Target(); - -protected: - BObjectList* fTarget; -}; - -{{#allobjs}} -class {{cppname}}_Stacked{{toplevelcppname}}JsonListener : public AbstractStacked{{toplevelcppname}}JsonListener { -public: - {{cppname}}_Stacked{{toplevelcppname}}JsonListener( - AbstractMain{{toplevelcppname}}JsonListener* mainListener, - AbstractStacked{{toplevelcppname}}JsonListener* parent); - ~{{cppname}}_Stacked{{toplevelcppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); - {{cppname}}* Target(); - -protected: - {{cppname}}* fTarget; - uint16 fNextItemBitmask; -}; - -class {{cppname}}_List_Stacked{{toplevelcppname}}JsonListener : public AbstractStacked{{toplevelcppname}}JsonListener { -public: - {{cppname}}_List_Stacked{{toplevelcppname}}JsonListener( - AbstractMain{{toplevelcppname}}JsonListener* mainListener, - AbstractStacked{{toplevelcppname}}JsonListener* parent); - ~{{cppname}}_List_Stacked{{toplevelcppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); - - BObjectList<{{cppname}}>* Target(); - // list of {{cppname}} pointers - -private: - BObjectList<{{cppname}}>* fTarget; -}; -{{/allobjs}} - - -class ItemEmittingStacked{{toplevel.cppname}}JsonListener : public {{toplevel.cppname}}_Stacked{{toplevel.cppname}}JsonListener { -public: - ItemEmittingStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent, - {{toplevel.cppname}}Listener* itemListener); - ~ItemEmittingStacked{{toplevel.cppname}}JsonListener(); - - bool WillPop(); - -private: - {{toplevel.cppname}}Listener* fItemListener; -}; - - -class BulkContainerStacked{{toplevel.cppname}}JsonListener : public AbstractStacked{{toplevel.cppname}}JsonListener { -public: - BulkContainerStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent, - {{toplevel.cppname}}Listener* itemListener); - ~BulkContainerStacked{{toplevel.cppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); - -private: - uint16 fNextItemIsItems; - {{toplevel.cppname}}Listener* fItemListener; -}; - - -class BulkContainerItemsStacked{{toplevel.cppname}}JsonListener : public AbstractStacked{{toplevel.cppname}}JsonListener { -public: - BulkContainerItemsStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent, - {{toplevel.cppname}}Listener* itemListener); - ~BulkContainerItemsStacked{{toplevel.cppname}}JsonListener(); - - bool Handle(const BJsonEvent& event); - bool WillPop(); - -private: - {{toplevel.cppname}}Listener* fItemListener; -}; - -// #pragma mark - implementations for the stacked listeners - -AbstractStacked{{toplevel.cppname}}JsonListener::AbstractStacked{{toplevel.cppname}}JsonListener ( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent) -{ - fMainListener = mainListener; - fParent = parent; -} - -AbstractStacked{{toplevel.cppname}}JsonListener::~AbstractStacked{{toplevel.cppname}}JsonListener() -{ -} - -void -AbstractStacked{{toplevel.cppname}}JsonListener::HandleError(status_t status, int32 line, const char* message) -{ - fMainListener->HandleError(status, line, message); -} - -void -AbstractStacked{{toplevel.cppname}}JsonListener::Complete() -{ - fMainListener->Complete(); -} - -status_t -AbstractStacked{{toplevel.cppname}}JsonListener::ErrorStatus() -{ - return fMainListener->ErrorStatus(); -} - -AbstractStacked{{toplevel.cppname}}JsonListener* -AbstractStacked{{toplevel.cppname}}JsonListener::Parent() -{ - return fParent; -} - -void -AbstractStacked{{toplevel.cppname}}JsonListener::Push(AbstractStacked{{toplevel.cppname}}JsonListener* stackedListener) -{ - fMainListener->SetStackedListener(stackedListener); -} - -bool -AbstractStacked{{toplevel.cppname}}JsonListener::WillPop() -{ - return true; -} - -bool -AbstractStacked{{toplevel.cppname}}JsonListener::Pop() -{ - bool result = WillPop(); - fMainListener->SetStackedListener(fParent); - return result; -} - -GeneralObjectStacked{{toplevel.cppname}}JsonListener::GeneralObjectStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent) - : - AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent) -{ -} - -GeneralObjectStacked{{toplevel.cppname}}JsonListener::~GeneralObjectStacked{{toplevel.cppname}}JsonListener() -{ -} - -bool -GeneralObjectStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) -{ - switch (event.EventType()) { - case B_JSON_OBJECT_NAME: - case B_JSON_NUMBER: - case B_JSON_STRING: - case B_JSON_TRUE: - case B_JSON_FALSE: - case B_JSON_NULL: - // ignore - break; - case B_JSON_OBJECT_START: - Push(new GeneralObjectStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); - break; - case B_JSON_ARRAY_START: - Push(new GeneralArrayStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); - break; - case B_JSON_ARRAY_END: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected end of array"); - break; - case B_JSON_OBJECT_END: - { - bool status = Pop() && (ErrorStatus() == B_OK); - delete this; - return status; - } - } - - return ErrorStatus() == B_OK; -} - -GeneralArrayStacked{{toplevel.cppname}}JsonListener::GeneralArrayStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent) - : - AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent) -{ -} - -GeneralArrayStacked{{toplevel.cppname}}JsonListener::~GeneralArrayStacked{{toplevel.cppname}}JsonListener() -{ -} - -bool -GeneralArrayStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) -{ - switch (event.EventType()) { - case B_JSON_OBJECT_NAME: - case B_JSON_NUMBER: - case B_JSON_STRING: - case B_JSON_TRUE: - case B_JSON_FALSE: - case B_JSON_NULL: - // ignore - break; - case B_JSON_OBJECT_START: - Push(new GeneralObjectStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); - break; - case B_JSON_ARRAY_START: - Push(new GeneralArrayStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); - break; - case B_JSON_OBJECT_END: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected end of object"); - break; - case B_JSON_ARRAY_END: - { - bool status = Pop() && (ErrorStatus() == B_OK); - delete this; - return status; - } - } - - - return ErrorStatus() == B_OK; -} - -StringList{{toplevel.cppname}}JsonListener::StringList{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, - AbstractStacked{{toplevel.cppname}}JsonListener* parent) - : - AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent) -{ - fTarget = new BObjectList(); -} - - -StringList{{toplevel.cppname}}JsonListener::~StringList{{toplevel.cppname}}JsonListener() -{ -} - - -BObjectList* -StringList{{toplevel.cppname}}JsonListener::Target() -{ - return fTarget; -} - - -bool -StringList{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) -{ - switch (event.EventType()) { - case B_JSON_ARRAY_END: - { - bool status = Pop() && (ErrorStatus() == B_OK); - delete this; - return status; - } - case B_JSON_STRING: - { - fTarget->AddItem(new BString(event.Content())); - break; - } - default: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, - "illegal state - unexpected json event parsing a string array"); - break; - } - - return ErrorStatus() == B_OK; -} - -{{#allobjs}} -{{cppname}}_Stacked{{toplevelcppname}}JsonListener::{{cppname}}_Stacked{{toplevelcppname}}JsonListener( - AbstractMain{{toplevelcppname}}JsonListener* mainListener, - AbstractStacked{{toplevelcppname}}JsonListener* parent) - : - AbstractStacked{{toplevelcppname}}JsonListener(mainListener, parent) -{ - fTarget = new {{cppname}}(); -} - - -{{cppname}}_Stacked{{toplevelcppname}}JsonListener::~{{cppname}}_Stacked{{toplevelcppname}}JsonListener() -{ -} - - -{{cppname}}* -{{cppname}}_Stacked{{toplevelcppname}}JsonListener::Target() -{ - return fTarget; -} - - -bool -{{cppname}}_Stacked{{toplevelcppname}}JsonListener::Handle(const BJsonEvent& event) -{ - switch (event.EventType()) { - case B_JSON_ARRAY_END: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected start of array"); - break; - case B_JSON_OBJECT_NAME: - fNextItemBitmask = 0; -{{#propertyarray}} if (0 == strcmp(event.Content(), "{{name}}")) - fNextItemBitmask = {{cppobjectname}}::k{{property.cppname}}Bitmask; -{{/propertyarray}} - break; - case B_JSON_OBJECT_END: - { - bool status = Pop() && (ErrorStatus() == B_OK); - delete this; - return status; - } - case B_JSON_STRING: - { -{{#propertyarray}}{{#property.isstring}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) - fTarget->Set{{property.cppname}}(new BString(event.Content())); -{{/property.isstring}}{{/propertyarray}} - fNextItemBitmask = 0; - break; - } - case B_JSON_TRUE: -{{#propertyarray}}{{#property.isboolean}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) - fTarget->Set{{property.cppname}}(true); -{{/property.isboolean}}{{/propertyarray}} - fNextItemBitmask = 0; - break; - case B_JSON_FALSE: -{{#propertyarray}}{{#property.isboolean}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) - fTarget->Set{{property.cppname}}(false); -{{/property.isboolean}} -{{/propertyarray}} - fNextItemBitmask = 0; - break; - case B_JSON_NULL: - { -{{#propertyarray}}{{^property.isarray}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) - fTarget->Set{{property.cppname}}Null(); -{{/property.isarray}}{{/propertyarray}} - fNextItemBitmask = 0; - break; - } - case B_JSON_NUMBER: - { -{{#propertyarray}}{{#property.isinteger}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) - fTarget->Set{{property.cppname}}(event.ContentInteger()); -{{/property.isinteger}} -{{#property.isnumber}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) - fTarget->Set{{property.cppname}}(event.ContentDouble()); -{{/property.isnumber}} -{{/propertyarray}} - fNextItemBitmask = 0; - break; - } - case B_JSON_OBJECT_START: - { -{{#propertyarray}}{{#property.isobject}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) { - GeneralObjectStacked{{toplevelcppname}}JsonListener* nextListener - = new GeneralObjectStacked{{toplevelcppname}}JsonListener(fMainListener, this); - Push(nextListener); - fNextItemBitmask = 0; - break; - } -{{/property.isobject}}{{/propertyarray}} - // a general consumer that will clear any spurious data that was not - // part of the schema. - GeneralObjectStacked{{toplevelcppname}}JsonListener* nextListener - = new GeneralObjectStacked{{toplevelcppname}}JsonListener(fMainListener, this); - Push(nextListener); - fNextItemBitmask = 0; - break; - } - case B_JSON_ARRAY_START: - { -{{#propertyarray}}{{#property.isarray}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) { - {{#property.items.isstring}} - StringList{{toplevelcppname}}JsonListener* nextListener = new StringList{{toplevelcppname}}JsonListener(fMainListener, this); - fTarget->Set{{property.cppname}}(nextListener->Target()); - {{/property.items.isstring}}{{^property.items.isstring}} - {{property.items.cppname}}_List_Stacked{{toplevelcppname}}JsonListener* nextListener = - new {{property.items.cppname}}_List_Stacked{{toplevelcppname}}JsonListener(fMainListener, this); - fTarget->Set{{property.cppname}}(nextListener->Target()); - {{/property.items.isstring}} - Push(nextListener); - fNextItemBitmask = 0; - break; - } -{{/property.isarray}}{{/propertyarray}} - // a general consumer that will clear any spurious data that was not - // part of the schema. - AbstractStacked{{toplevelcppname}}JsonListener* nextListener - = new GeneralArrayStacked{{toplevelcppname}}JsonListener(fMainListener, this); - Push(nextListener); - fNextItemBitmask = 0; - break; - } - } - - return ErrorStatus() == B_OK; -} - - -{{cppname}}_List_Stacked{{toplevelcppname}}JsonListener::{{cppname}}_List_Stacked{{toplevelcppname}}JsonListener( - AbstractMain{{toplevelcppname}}JsonListener* mainListener, - AbstractStacked{{toplevelcppname}}JsonListener* parent) - : - AbstractStacked{{toplevelcppname}}JsonListener(mainListener, parent) -{ - fTarget = new BObjectList<{{cppname}}>(); -} - - -{{cppname}}_List_Stacked{{toplevelcppname}}JsonListener::~{{cppname}}_List_Stacked{{toplevelcppname}}JsonListener() -{ -} - - -BObjectList<{{cppname}}>* -{{cppname}}_List_Stacked{{toplevelcppname}}JsonListener::Target() -{ - return fTarget; -} - - -bool -{{cppname}}_List_Stacked{{toplevelcppname}}JsonListener::Handle(const BJsonEvent& event) -{ - switch (event.EventType()) { - case B_JSON_ARRAY_END: - { - bool status = Pop() && (ErrorStatus() == B_OK); - delete this; - return status; - } - case B_JSON_OBJECT_START: - { - {{cppname}}_Stacked{{toplevelcppname}}JsonListener* nextListener = - new {{cppname}}_Stacked{{toplevelcppname}}JsonListener(fMainListener, this); - fTarget->AddItem(nextListener->Target()); - Push(nextListener); - break; - } - default: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, - "illegal state - unexpected json event parsing an array of {{cppname}}"); - break; - } - - return ErrorStatus() == B_OK; -} -{{/allobjs}} - -ItemEmittingStacked{{toplevel.cppname}}JsonListener::ItemEmittingStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, AbstractStacked{{toplevel.cppname}}JsonListener* parent, - {{toplevel.cppname}}Listener* itemListener) -: -{{toplevel.cppname}}_Stacked{{toplevel.cppname}}JsonListener(mainListener, parent) -{ - fItemListener = itemListener; -} - - -ItemEmittingStacked{{toplevel.cppname}}JsonListener::~ItemEmittingStacked{{toplevel.cppname}}JsonListener() -{ -} - - -bool -ItemEmittingStacked{{toplevel.cppname}}JsonListener::WillPop() -{ - bool result = fItemListener->Handle(fTarget); - delete fTarget; - fTarget = NULL; - return result; -} - - -BulkContainerStacked{{toplevel.cppname}}JsonListener::BulkContainerStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, AbstractStacked{{toplevel.cppname}}JsonListener* parent, - {{toplevel.cppname}}Listener* itemListener) -: -AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent) -{ - fItemListener = itemListener; -} - - -BulkContainerStacked{{toplevel.cppname}}JsonListener::~BulkContainerStacked{{toplevel.cppname}}JsonListener() -{ -} - - -bool -BulkContainerStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) -{ - switch (event.EventType()) { - case B_JSON_ARRAY_END: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected end of array"); - break; - case B_JSON_OBJECT_NAME: - fNextItemIsItems = (0 == strcmp(event.Content(), "items")); - break; - case B_JSON_OBJECT_START: - Push(new GeneralObjectStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); - fNextItemIsItems = false; - break; - case B_JSON_ARRAY_START: - if (fNextItemIsItems) - Push(new BulkContainerItemsStacked{{toplevel.cppname}}JsonListener(fMainListener, this, fItemListener)); - else - Push(new GeneralArrayStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); - break; - case B_JSON_OBJECT_END: - { - bool status = Pop() && (ErrorStatus() == B_OK); - delete this; - return status; - } - default: - // ignore any other fields - fNextItemIsItems = false; - break; - } - - return ErrorStatus() == B_OK; -} - - -BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::BulkContainerItemsStacked{{toplevel.cppname}}JsonListener( - AbstractMain{{toplevel.cppname}}JsonListener* mainListener, AbstractStacked{{toplevel.cppname}}JsonListener* parent, - {{toplevel.cppname}}Listener* itemListener) -: -AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent) -{ - fItemListener = itemListener; -} - - -BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::~BulkContainerItemsStacked{{toplevel.cppname}}JsonListener() -{ -} - - -bool -BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) -{ - switch (event.EventType()) { - case B_JSON_OBJECT_START: - Push(new ItemEmittingStacked{{toplevel.cppname}}JsonListener(fMainListener, this, fItemListener)); - break; - case B_JSON_ARRAY_END: - { - bool status = Pop() && (ErrorStatus() == B_OK); - delete this; - return status; - } - default: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected json event"); - break; - } - - return ErrorStatus() == B_OK; -} - - -bool -BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::WillPop() -{ - fItemListener->Complete(); - return true; -} - - -// #pragma mark - implementations for the main listeners - - -AbstractMain{{toplevel.cppname}}JsonListener::AbstractMain{{toplevel.cppname}}JsonListener() -{ - fStackedListener = NULL; - fErrorStatus = B_OK; -} - - -AbstractMain{{toplevel.cppname}}JsonListener::~AbstractMain{{toplevel.cppname}}JsonListener() -{ -} - - -void -AbstractMain{{toplevel.cppname}}JsonListener::HandleError(status_t status, int32 line, const char* message) -{ - if (message != NULL) { - fprintf(stderr, "an error has arisen processing json for '{{toplevel.cppname}}'; %s\\n", message); - } else { - fprintf(stderr, "an error has arisen processing json for '{{toplevel.cppname}}'\\n"); - } - fErrorStatus = status; -} - - -void -AbstractMain{{toplevel.cppname}}JsonListener::Complete() -{ -} - - -status_t -AbstractMain{{toplevel.cppname}}JsonListener::ErrorStatus() -{ - return fErrorStatus; -} - -void -AbstractMain{{toplevel.cppname}}JsonListener::SetStackedListener( - AbstractStacked{{toplevel.cppname}}JsonListener* stackedListener) -{ - fStackedListener = stackedListener; -} - - -Single{{toplevel.cppname}}JsonListener::Single{{toplevel.cppname}}JsonListener() - : - AbstractMain{{toplevel.cppname}}JsonListener() -{ - fTarget = NULL; -} - - -Single{{toplevel.cppname}}JsonListener::~Single{{toplevel.cppname}}JsonListener() -{ -} - - -bool -Single{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) -{ - if (fErrorStatus != B_OK) - return false; - - if (fStackedListener != NULL) - return fStackedListener->Handle(event); - - switch (event.EventType()) { - case B_JSON_OBJECT_START: - { - {{toplevel.cppname}}_Stacked{{toplevel.cppname}}JsonListener* nextListener - = new {{toplevel.cppname}}_Stacked{{toplevel.cppname}}JsonListener(this, NULL); - fTarget = nextListener->Target(); - SetStackedListener(nextListener); - break; - } - default: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, - "illegal state - unexpected json event parsing top level for {{toplevel.cppname}}"); - break; - } - - return ErrorStatus() == B_OK; -} - - -{{toplevel.cppname}}* -Single{{toplevel.cppname}}JsonListener::Target() -{ - return fTarget; -} - - -BulkContainer{{toplevel.cppname}}JsonListener::BulkContainer{{toplevel.cppname}}JsonListener( - {{toplevel.cppname}}Listener* itemListener) : AbstractMain{{toplevel.cppname}}JsonListener() -{ - fItemListener = itemListener; -} - - -BulkContainer{{toplevel.cppname}}JsonListener::~BulkContainer{{toplevel.cppname}}JsonListener() -{ -} - - -bool -BulkContainer{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) -{ - if (fErrorStatus != B_OK) - return false; - - if (fStackedListener != NULL) - return fStackedListener->Handle(event); - - switch (event.EventType()) { - case B_JSON_OBJECT_START: - { - BulkContainerStacked{{toplevel.cppname}}JsonListener* nextListener = - new BulkContainerStacked{{toplevel.cppname}}JsonListener(this, NULL, fItemListener); - SetStackedListener(nextListener); - return true; - break; - } - default: - HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, - "illegal state - unexpected json event parsing top level for BulkContainerStacked{{toplevel.cppname}}JsonListener"); - break; - } - - return ErrorStatus() == B_OK; -} -""" - - -def write_files(schema : dict[str, any], output_directory: str) -> None: - cpp_listener_name = schema["cppname"] + "JsonListener" - cpp_header_filename = os.path.join(output_directory, cpp_listener_name + '.h') - cpp_implementation_filename = os.path.join(output_directory, cpp_listener_name + '.cpp') - - model = dict[string, any]() - - model['toplevel'] = schema - model['allobjs'] = hdsjsonschemacommon.collect_all_objects(schema) - - with open(cpp_header_filename, 'w') as cpp_h_file: - cpp_h_file.write(ustache.render( - HEADER_TEMPLATE, - model, - escape= lambda x: x)) - - with open(cpp_implementation_filename, 'w') as cpp_i_file: - cpp_i_file.write(ustache.render( - IMPLEMENTATION_TEMPLATE, - model, - escape= lambda x: x)) def main(): parser = argparse.ArgumentParser(description='Convert JSON schema to Haiku C++ Parsers') - parser.add_argument('-i', '--inputfile', required=True, help='The input filename containing the JSON schema') - parser.add_argument('--outputdirectory', help='The output directory where the C++ files should be written') + + parser.add_argument( + "--jsonschemafile", + dest = "jsonschemafile", + required=True, + help="The input filename containing the JSON schema") + + parser.add_argument( + "--outputdirectory", + dest = "outputdirectory", + required=True, + help="The output directory where the C++ header and implementation file should be written") args = parser.parse_args() - outputdirectory = args.outputdirectory + schema_path = Path(args.jsonschemafile).absolute() - if not outputdirectory: - outputdirectory = '.' + if not schema_path.is_file(): + raise RuntimeError(f"The input file [{schema_path}] does not exist") - with open(args.inputfile) as inputfile: - schema = json.load(inputfile) - hdsjsonschemacommon.augment_schema(schema) - write_files(schema, outputdirectory) + output_directory_path = Path(args.outputdirectory).absolute() + output_directory_path.mkdir(parents=True, exist_ok=True) + with open(schema_path) as input_file: + schema = json.load(input_file) + + jsonschemacommon.augment_schema(schema) + object_schemas = jsonschemacommon.collect_all_objects(schema) + + def output_to_path(output_path: pathlib.Path): + template_type = jsonschemacommon.path_to_extension_type(output_path) + template = jsonschemacommon.load_template("parser", template_type) + template_model = { + "toplevel": schema, + "allobjs": object_schemas, + "outputfilename": output_path.name, + "outputheaderfile": output_path.stem + ".h", + "outputfilestemupper": output_path.stem.upper() + } + + with open(output_path, 'w') as out_file: + out_file.write(ustache.render( + template, + template_model, + escape= lambda x: x)) + + cpp_name = schema["cppname"] + output_to_path(output_directory_path / f"{cpp_name}JsonListener.h") + output_to_path(output_directory_path / f"{cpp_name}JsonListener.cpp") if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/apps/haikudepot/build/scripts/jsonschemacommon.py b/src/apps/haikudepot/build/scripts/jsonschemacommon.py new file mode 100644 index 0000000000..390759f428 --- /dev/null +++ b/src/apps/haikudepot/build/scripts/jsonschemacommon.py @@ -0,0 +1,335 @@ +# ===================================== +# Copyright 2017-2026, Andrew Lindesay +# Distributed under the terms of the MIT License. +# ===================================== + +"""\ +This file contains classes and helper functions that are used in the generation +of `.h` and `.cpp` C++ files based on a JSON Schema. The schemas are intended +for use in a Java setting, but are adapted by this logic for use with C++ and +types that are typical for the Haiku environment. +""" + +import datetime +import pathlib +from typing import Any +from enum import Enum, auto + +# The possible JSON types +JSON_TYPE_STRING = "string" +JSON_TYPE_OBJECT = "object" +JSON_TYPE_ARRAY = "array" +JSON_TYPE_BOOLEAN = "boolean" +JSON_TYPE_INTEGER = "integer" +JSON_TYPE_NUMBER = "number" + + +# The possible C++ types +CPP_TYPE_STRING = "BString" +CPP_TYPE_ARRAY = "std::vector" +CPP_TYPE_BOOLEAN = "bool" +CPP_TYPE_INTEGER = "int64" +CPP_TYPE_NUMBER = "double" + + +# The possible C++ default values +CPP_DEFAULT_STRING = "\"\"" +CPP_DEFAULT_BOOLEAN = "false" +CPP_DEFAULT_INTEGER = "0" +CPP_DEFAULT_NUMBER = "0.0" + + +class TemplateType(Enum): + """Type of template to load.""" + HEADER = auto() + IMPLEMENTATION = auto() + + +class CppClassNode: + """An object representing a C++ class for establishing a dependency graph.""" + def __init__(self, cpp_name: str, schema: dict[str, Any], dependencies: set): + self.cpp_name = cpp_name + self.schema = schema + self.dependencies = dependencies + + +def java_type_to_cpp_name(java_name: str) -> str: + return java_name[java_name.rindex('.') + 1:] + + +def prop_name_to_cpp_name(prop_name: str) -> str: + return prop_name[0:1].upper() + prop_name[1:] + + +def prop_name_to_cpp_member_name(prop_name: str) -> str: + return "f" + prop_name_to_cpp_name(prop_name) + + +def prop_metadata_to_cpp_default_value(prop_metadata: dict[str, Any]) -> str: + """Converts the property data into a C++ default for the field. + + This function will look at the metadata for the property and decide what a + generic default value for that type might be. + + Returns: The function will return a string representation of the C++ value. + """ + type = prop_metadata['type'] + + if type == JSON_TYPE_STRING: + return CPP_DEFAULT_STRING + if type == JSON_TYPE_BOOLEAN: + return CPP_DEFAULT_BOOLEAN + if type == JSON_TYPE_INTEGER: + return CPP_DEFAULT_INTEGER + if type == JSON_TYPE_NUMBER: + return CPP_DEFAULT_NUMBER + if type == JSON_TYPE_OBJECT: + cpp_type = prop_metadata["cpptype"] + return f"{cpp_type}Ref()" + if type == JSON_TYPE_ARRAY: + items_cpptype = prop_metadata["items"]["cpptype"] + return f"{CPP_TYPE_ARRAY}<{items_cpptype}>()" + + raise Exception(f"unknown json-schema type [{type}]") + +def prop_metadata_to_cpp_type_name(prop_metadata: dict[str, Any]) -> str: + """Introspects data metadata for a property and returns its type in C++""" + type_ = prop_metadata['type'] + + if type_ == JSON_TYPE_STRING: + return CPP_TYPE_STRING + if type_ == JSON_TYPE_BOOLEAN: + return CPP_TYPE_BOOLEAN + if type_ == JSON_TYPE_INTEGER: + return CPP_TYPE_INTEGER + if type_ == JSON_TYPE_NUMBER: + return CPP_TYPE_NUMBER + if type_ == JSON_TYPE_OBJECT: + java_type = prop_metadata['javaType'] + + if not java_type or 0 == len(java_type): + raise Exception('missing "javaType" field') + + return java_type_to_cpp_name(java_type) + + if type_ == JSON_TYPE_ARRAY: + items_cpptype = prop_metadata["items"]["cpptype"] + return f"{CPP_TYPE_ARRAY}<{items_cpptype}>" + + raise Exception(f"unknown json-schema type [{type_}]") + + +def prop_metadata_type_is_scalar(prop_metadata: dict[str, Any]) -> bool: + type_ = prop_metadata['type'] + return type_ == JSON_TYPE_BOOLEAN or type_ == JSON_TYPE_INTEGER or type_ == JSON_TYPE_NUMBER + + +def write_top_comment(f, input_filename: str, variant: str) -> None: + f.write(( + '/*\n' + ' * Generated %s Object\n' + ' * source json-schema : %s\n' + ' * generated at : %s\n' + ' */\n' + ) % (variant, input_filename, datetime.datetime.now().isoformat())) + + +def _to_cpp_class_dependency_tree(schema: dict[str, Any]) -> CppClassNode | None: + """Builds up a dependency tree of to-be C++ classes + + The output of this is a dependency tree of C++ classes from which it is + possible to get the order that the classes need to be written into the + header and implementation files. + """ + + all_nodes: list[CppClassNode] = [] + + def to_node(sub_schema: dict[str, Any]): + if sub_schema["type"] == JSON_TYPE_ARRAY: + return to_node(sub_schema["items"]) + if sub_schema["type"] == JSON_TYPE_OBJECT: + cpp_name = sub_schema["cppname"] + + existing_class = next((c for c in all_nodes if c.cpp_name == cpp_name), None) + if existing_class: + return existing_class + + node = CppClassNode(cpp_name=cpp_name, schema=sub_schema, dependencies=set()) + all_nodes.append(node) + + dependencies = [to_node(property_schema) for property_schema in sub_schema["properties"].values()] + node.dependencies = sorted([d for d in dependencies if d], key = lambda d: d.cpp_name) + + return node + return None + + return to_node(schema) + + +def _to_object_sub_schemas_in_dependency_order(root_node: CppClassNode) -> list[dict[str, Any]]: + """Returns a list of the schemas representing objects such that any dependencies appear first.""" + + result: list[dict[str, Any]] = [] + + def walk(node: CppClassNode): + for d in node.dependencies: + walk(d) + result.append(node.schema) + + walk(root_node) + + return result + + +def collect_all_objects(schema: dict[str, Any]) -> list[dict[str, Any]]: + """Walks the schema and extracts all definitions of objects. + + Returns: + A list of schema structure which defines the class. The list will be in + order so that latter classes are able to have dependency on earlier + classes. + """ + + root_node = _to_cpp_class_dependency_tree(schema) + + if not root_node: + raise RuntimeError("it was not possible to extract a tree from the schema data") + + return _to_object_sub_schemas_in_dependency_order(root_node) + + +def augment_schema(schema: dict[str, Any]) -> None: + """This function will take the data from the JSON schema and will overlay any + specific information required for rendering the templates. + """ + + def augment_property_type(prop: dict[str, Any]) -> None: + prop_type = prop['type'] + + if not prop_type or 0 == len(prop_type): + raise Exception('missing "type" field') + + # For the object type, a `BReference` is used as the member variable + # type but the actual wrapped type is still required for constructors. + + if prop_type == JSON_TYPE_OBJECT: + prop["cpptyperaw"] = prop_metadata_to_cpp_type_name(prop) + prop["cpptype"] = prop_metadata_to_cpp_type_name(prop) + "Ref" + else: + prop["cpptype"] = prop_metadata_to_cpp_type_name(prop) + + is_scalar_type = prop_metadata_type_is_scalar(prop) + is_array_type = (prop_type == JSON_TYPE_ARRAY) + + prop["iscppscalartype"] = is_scalar_type + prop["isarray"] = is_array_type + prop["isstring"] = JSON_TYPE_STRING == prop_type + prop["isboolean"] = JSON_TYPE_BOOLEAN == prop_type + prop["isnumber"] = JSON_TYPE_NUMBER == prop_type + prop["isinteger"] = JSON_TYPE_INTEGER == prop_type + prop["isobject"] = JSON_TYPE_OBJECT == prop_type + prop["isobjectorarray"] = prop_type in [JSON_TYPE_OBJECT, JSON_TYPE_ARRAY] + prop["iscppnonscalarnoncollectiontype"] = not is_scalar_type and not is_array_type + + def derive_cpp_classname(obj: dict[str, Any]) -> str: + obj_type = obj['type'] + + if obj_type != JSON_TYPE_OBJECT: + raise Exception('expecting object, but was [' + obj_type + ']') + + java_type = obj['javaType'] + + return java_type_to_cpp_name(java_type) + + def augment_property(prop: dict[str, Any]) -> None: + + if JSON_TYPE_ARRAY == prop["type"]: + array_items = prop['items'] + array_items_type = array_items["type"] + + if array_items_type == JSON_TYPE_OBJECT: + augment_object(array_items) + elif array_items_type == JSON_TYPE_STRING: + augment_property(array_items) + else: + raise Exception(f"bad type for array items [{array_items_type}]") + + augment_property_type(prop) + prop["cppdefaultvalue"] = prop_metadata_to_cpp_default_value(prop) + + if JSON_TYPE_OBJECT == prop["type"]: + augment_object(prop) + + def augment_object(obj: dict[str, Any]) -> None: + def has_any_list_properties() -> bool: + for _, prop in obj['properties'].items(): + if prop['type'] == JSON_TYPE_ARRAY: + return True + return False + + augment_property_type(obj) + + obj_cpp_classname = derive_cpp_classname(obj) + obj["cppname"] = obj_cpp_classname + obj["cppnameupper"] = obj_cpp_classname.upper() + obj["hasanylistproperties"] = has_any_list_properties() + + # allows the object to know the top level object that contains it + obj["toplevelcppname"] = top_level_cpp_name + + properties = obj['properties'].items() + + for prop_name, prop in properties: + prop["cppname"] = prop_name_to_cpp_name(prop_name) + prop["cppmembername"] = prop_name_to_cpp_member_name(prop_name) + augment_property(prop) + + # mustache is not able to iterate over a dictionary; it can only + # iterate over a list where each item in the list has some properties. + + property_array = sorted( + [{ + "name": k, + "property": v, + "cppobjectname": obj_cpp_classname + # ^ this is the name of the containing object + } for k,v in properties], + key= lambda item: item["name"] + ) + + for i in range(len(property_array)): + property_array[i]["cppbitmaskexpression"] = "(1 << %d)" % i + + if 0 != len(property_array): + property_array[0]["isfirst"] = True + property_array[len(property_array) - 1]["islast"] = True + + obj["propertyarray"] = property_array + + top_level_cpp_name = derive_cpp_classname(schema) + augment_object(schema) + + +def path_to_extension_type(file_path: pathlib.Path) -> TemplateType: + if ".h" == file_path.suffix: + return TemplateType.HEADER + if ".cpp" == file_path.suffix: + return TemplateType.IMPLEMENTATION + raise RuntimeError(f"unknown extension [{file_path.suffix}]") + + +def load_template(template_name: str, template_type: TemplateType) -> str: + """Loads the template from the `./template` directory.""" + + def template_type_path_component() -> str: + if template_type == TemplateType.HEADER: + return "header" + if template_type == TemplateType.IMPLEMENTATION: + return "implementation" + raise RuntimeError(f"unknown template type [{str(template_type)}") + + script_directory = pathlib.Path(__file__).parent.resolve() + template_path = script_directory / "template" / f"{template_name}{template_type_path_component()}.mustache" + + with open(template_path, "r") as f: + return f.read() diff --git a/src/apps/haikudepot/build/scripts/template/modelheader.mustache b/src/apps/haikudepot/build/scripts/template/modelheader.mustache new file mode 100644 index 0000000000..88accb30c6 --- /dev/null +++ b/src/apps/haikudepot/build/scripts/template/modelheader.mustache @@ -0,0 +1,101 @@ +/* + * Generated Model Object for {{outputfilename}} + */ + +#ifndef GEN_JSON_SCHEMA_MODEL__{{outputfilestemupper}}_H +#define GEN_JSON_SCHEMA_MODEL__{{outputfilestemupper}}_H + +#include + +#include +#include +#include {{#objects}} + + +class {{cppname}} : public BArchivable, public BReferenceable +{ +friend class {{cppname}}Builder; + +public: + {{cppname}}(); + {{cppname}}(const BMessage* message); + virtual ~{{cppname}}(); + + bool operator==(const {{cppname}}& other) const; + bool operator!=(const {{cppname}}& other) const; + +{{#propertyarray}}{{#property.isarray}} + const {{property.cpptype}} {{property.cppname}}() const; + int32 Count{{property.cppname}}() const; + const {{property.items.cpptype}}& {{property.cppname}}ItemAt(int32 index) const; + bool IsSet{{property.cppname}}() const; +{{/property.isarray}}{{^property.isarray}} +{{#property.iscppscalartype}} {{property.cpptype}} {{property.cppname}}() const; +{{/property.iscppscalartype}}{{^property.iscppscalartype}} const {{property.cpptype}}& {{property.cppname}}() const; +{{/property.iscppscalartype}} + bool IsSet{{property.cppname}}() const; +{{/property.isarray}} +{{/propertyarray}} + + status_t Archive(BMessage* into, bool deep = true) const; + +private: +{{#propertyarray}}{{#property.isarray}} void _Set{{property.cppname}}(const {{property.cpptype}}& value); + void _AddTo{{property.cppname}}(const {{property.items.cpptype}}& value); + void _Unset{{property.cppname}}(); +{{/property.isarray}}{{^property.isarray}} +{{#property.iscppscalartype}} void _Set{{property.cppname}}({{property.cpptype}} value); +{{/property.iscppscalartype}}{{^property.iscppscalartype}} void _Set{{property.cppname}}(const {{property.cpptype}}& value); +{{/property.iscppscalartype}} + void _Unset{{property.cppname}}(); +{{/property.isarray}} +{{/propertyarray}} + +public: +{{#propertyarray}} static const uint16 k{{property.cppname}}Bitmask; +{{/propertyarray}} + +private: + uint16 fHasValueBitmask; +{{#propertyarray}} {{property.cpptype}} {{property.cppmembername}}; +{{/propertyarray}} +}; + + +typedef BReference<{{cppname}}> {{cppname}}Ref; + + +class {{cppname}}Builder +{ +public: + {{cppname}}Builder(); + {{cppname}}Builder(const {{cppname}}Ref& source); + {{cppname}}Builder(const {{cppname}}* source); + virtual ~{{cppname}}Builder(); + + {{cppname}}Builder& Clear(); + {{cppname}}Ref BuildRef(); + +{{#propertyarray}}{{#property.isarray}} + {{cppname}}Builder& With{{property.cppname}}({{property.cpptype}} value); + {{cppname}}Builder& Without{{property.cppname}}(); + {{cppname}}Builder& AddTo{{property.cppname}}({{property.items.cpptype}} value); +{{/property.isarray}}{{^property.isarray}} + {{cppname}}Builder& With{{property.cppname}}({{property.cpptype}} value); + {{cppname}}Builder& Without{{property.cppname}}(); +{{/property.isarray}} +{{/propertyarray}} + +private: + void _InitFromSource(); + void _Init(const {{cppname}}* source); + +private: + uint16 fHasValueBitmask; + {{cppname}}Ref fSourceRef; + +{{#propertyarray}} {{property.cpptype}} {{property.cppmembername}}; +{{/propertyarray}} +};{{/objects}} + +#endif // GEN_JSON_SCHEMA_MODEL__{{outputfilestemupper}}_H \ No newline at end of file diff --git a/src/apps/haikudepot/build/scripts/template/modelimplementation.mustache b/src/apps/haikudepot/build/scripts/template/modelimplementation.mustache new file mode 100644 index 0000000000..eed830781c --- /dev/null +++ b/src/apps/haikudepot/build/scripts/template/modelimplementation.mustache @@ -0,0 +1,325 @@ +/* + * Generated Model Object for {{outputfilename}} + */ + +#include "{{outputheaderfile}}"{{#objects}} + + +{{#propertyarray}}/*static*/ const uint16 {{cppname}}::k{{property.cppname}}Bitmask = {{cppbitmaskexpression}}; +{{/propertyarray}} + + +{{cppname}}::{{cppname}}() + : + fHasValueBitmask(0), +{{#propertyarray}} {{property.cppmembername}}({{property.cppdefaultvalue}}){{^islast}},{{/islast}} +{{/propertyarray}}{ +} + + +{{cppname}}::{{cppname}}(const BMessage* from) + : + fHasValueBitmask(0) +{ +{{#hasanylistproperties}} bool arrayIsSet; + BString arrayItemKey; +{{/hasanylistproperties}} +{{#propertyarray}}{{#property.isstring}} if (from->FindString("{{property.cppname}}", &{{property.cppmembername}}) == B_OK) + fHasValueBitmask |= k{{property.cppname}}Bitmask; +{{/property.isstring}}{{#property.isboolean}} if (from->FindBool("{{property.cppname}}", &{{property.cppmembername}}) == B_OK) + fHasValueBitmask |= k{{property.cppname}}Bitmask; +{{/property.isboolean}}{{#property.isnumber}} if (from->FindDouble("{{property.cppname}}", &{{property.cppmembername}}) == B_OK) + fHasValueBitmask |= k{{property.cppname}}Bitmask; +{{/property.isnumber}}{{#property.isinteger}} if (from->FindInt64("{{property.cppname}}", &{{property.cppmembername}}) == B_OK) + fHasValueBitmask |= k{{property.cppname}}Bitmask; +{{/property.isinteger}}{{#property.isobject}} { + BMessage objectMessage; + if (from->FindMessage("{{property.cppname}}", &objectMessage) == B_OK) { + {{property.cpptyperaw}}* object = new {{property.cpptyperaw}}(&objectMessage); + {{property.cppmembername}} = {{property.cpptyperaw}}(object, true); + fHasValueBitmask |= k{{property.cppname}}Bitmask; + } + } +{{/property.isobject}}{{#property.isarray}} if (from->FindBool("{{property.cppname}}_isSet", &arrayIsSet) == B_OK && arrayIsSet) { + status_t arrayItemFindResult = B_OK; + + for (int32 i = 0; arrayItemFindResult == B_OK; i++) { + arrayItemKey.SetToFormat("{{property.cppname}}_%" B_PRId32, i); +{{#property.items.isstring}} BString arrayItemString; + arrayItemFindResult = from->FindString(arrayItemKey, &arrayItemString); + if (arrayItemFindResult == B_OK) + {{property.cppmembername}}.push_back(arrayItemString); +{{/property.items.isstring}}{{^property.items.isstring}} BMessage arrayItemMessage; + arrayItemFindResult = from->FindMessage(arrayItemKey, &arrayItemMessage); + if (arrayItemFindResult == B_OK) { + {{property.items.cpptyperaw}}* arrayItemObject = new {{property.items.cpptyperaw}}(&arrayItemMessage); + {{property.cppmembername}}.push_back({{property.items.cpptype}}(arrayItemObject, true)); + } +{{/property.items.isstring}} } + } +{{/property.isarray}}{{/propertyarray}} +} + + +{{cppname}}::~{{cppname}}() +{ +} + + +bool +{{cppname}}::operator==(const {{cppname}}& other) const +{ + if (fHasValueBitmask != other.fHasValueBitmask) + return false; +{{#propertyarray}}{{#property.iscppscalartype}} if (0 != (fHasValueBitmask & k{{property.cppname}}Bitmask) && {{property.cppmembername}} != other.{{property.cppmembername}}) + return false; +{{/property.iscppscalartype}}{{/propertyarray}}{{#propertyarray}}{{#property.isstring}} if (0 != (fHasValueBitmask & k{{property.cppname}}Bitmask) && {{property.cppmembername}} != other.{{property.cppmembername}}) + return false; +{{/property.isstring}}{{/propertyarray}}{{#propertyarray}}{{#property.isobject}} if (0 != (fHasValueBitmask & k{{property.cppname}}Bitmask) && *({{property.cppmembername}}.Get()) != *(other.{{property.cppmembername}}.Get()) + return false; +{{/property.isobject}}{{/propertyarray}}{{#propertyarray}}{{#property.isarray}} if (0 != (fHasValueBitmask & k{{property.cppname}}Bitmask)) { + if ({{property.cppmembername}}.size() != other.{{property.cppmembername}}.size()) + return false; + for (int32 i = {{property.cppmembername}}.size() - 1; i >= 0; i--) { +{{#property.items.isobject}} if (*({{property.cppmembername}}[i].Get()) != *(other.{{property.cppmembername}}[i].Get())) + return false; +{{/property.items.isobject}}{{^property.items.isobject}} if ({{property.cppmembername}}[i] != other.{{property.cppmembername}}[i]) + return false; +{{/property.items.isobject}} + } + } +{{/property.isarray}}{{/propertyarray}} + return true; +} + + +bool +{{cppname}}::operator!=(const {{cppname}}& other) const +{ + return !(*this == other); +} + + +{{#propertyarray}}void +{{#property.iscppscalartype}}{{cppname}}::_Set{{property.cppname}}({{property.cpptype}} value) +{{/property.iscppscalartype}}{{^property.iscppscalartype}}{{cppname}}::_Set{{property.cppname}}(const {{property.cpptype}}& value) +{{/property.iscppscalartype}}{ +{{#property.isobject}} if (!{{property.cpptype}}.IsSet()) + fHasValueBitmask &= ~k{{property.cppname}}Bitmask; + else + fHasValueBitmask |= k{{property.cppname}}Bitmask; +{{/property.isobject}}{{^property.isobject}} fHasValueBitmask |= k{{property.cppname}}Bitmask; +{{/property.isobject}} {{property.cppmembername}} = value; +} + + +void +{{cppname}}::_Unset{{property.cppname}}() +{ + fHasValueBitmask &= ~k{{property.cppname}}Bitmask; + {{property.cppmembername}} = {{property.cppdefaultvalue}}; +} + + +bool +{{cppname}}::IsSet{{property.cppname}}() const +{ + return 0 != (fHasValueBitmask & k{{property.cppname}}Bitmask); +} + + +{{#property.isarray}}void +{{cppname}}::_AddTo{{property.cppname}}(const {{property.items.cpptype}}& value) +{ +{{#property.items.isobject}} if (!value.IsSet()) { + debugger("attempt to inset an unset object into an array field [{{property.cppmembername}}] of [{{cppobjectname}}]"); + return; + } +{{/property.items.isobject}} fHasValueBitmask |= k{{property.cppname}}Bitmask; + {{property.cppmembername}}.push_back(value); +} + + +const {{property.cpptype}} +{{cppname}}::{{property.cppname}}() const +{ + return {{property.cppmembername}}; +} + + +int32 +{{cppname}}::Count{{property.cppname}}() const +{ + return {{property.cppmembername}}.size(); +} + + +const {{property.items.cpptype}}& +{{cppname}}::{{property.cppname}}ItemAt(int32 index) const +{ + return {{property.cppmembername}}[index]; +} + + +{{/property.isarray}}{{^property.isarray}}{{#property.iscppscalartype}}{{property.cpptype}} +{{cppname}}::{{property.cppname}}() const +{{/property.iscppscalartype}}{{^property.iscppscalartype}}const {{property.cpptype}}& +{{cppname}}::{{property.cppname}}() const +{{/property.iscppscalartype}}{ + return {{property.cppmembername}}; +} + + +{{/property.isarray}}{{/propertyarray}} +status_t +{{cppname}}::Archive(BMessage* into, bool deep) const +{ +{{#hasanylistproperties}} BString arrayItemKey;{{/hasanylistproperties}} + status_t result = B_OK; + +{{#propertyarray}}{{#property.isstring}} if (result == B_OK && 0 != (fHasValueBitmask & k{{property.cppname}}Bitmask)) + result = into->AddString("{{property.cppname}}", {{property.cppmembername}}); +{{/property.isstring}}{{#property.isboolean}} if (result == B_OK && 0 != (fHasValueBitmask & k{{property.cppname}}Bitmask)) + result = into->AddBool("{{property.cppname}}", {{property.cppmembername}}); +{{/property.isboolean}}{{#property.isnumber}} if (result == B_OK && 0 != (fHasValueBitmask & k{{property.cppname}}Bitmask)) + result = into->AddDouble("{{property.cppname}}", {{property.cppmembername}}); +{{/property.isnumber}}{{#property.isinteger}} if (result == B_OK && 0 != (fHasValueBitmask & k{{property.cppname}}Bitmask)) + result = into->AddInt64("{{property.cppname}}", {{property.cppmembername}}); +{{/property.isinteger}}{{#property.isobject}} if (deep && result == B_OK && 0 != (fHasValueBitmask & k{{property.cppname}}Bitmask && {{property.cppmembername}}.IsSet())) { + BMessage objectMessage; + result = {{property.cppmembername}}->Archive(&objectMessage, deep); + if (result == B_OK) + result = into->AddMessage("{{property.cppname}}", &objectMessage); + } +{{/property.isobject}}{{#property.isarray}} if (deep && result == B_OK && 0 != (fHasValueBitmask & k{{property.cppname}}Bitmask)) { + for (int32 i = Count{{property.cppname}}() - 1; result == B_OK && i >= 0; i--) { + arrayItemKey.SetToFormat("%s_%" B_PRId32, "{{property.cppname}}", i); +{{#property.items.isstring}} result = into->AddString(arrayItemKey, {{property.cppname}}ItemAt(i)); +{{/property.items.isstring}}{{#property.items.isobject}} BMessage arrayItemMessage; + result = {{property.cppname}}ItemAt(i)->Archive(&arrayItemMessage, deep); + if (result == B_OK) + result = into->AddMessage(arrayItemKey, &arrayItemMessage); +{{/property.items.isobject}} } + if (result == B_OK) + result = into->AddBool("{{property.cppname}}_isSet", true); + } +{{/property.isarray}} +{{/propertyarray}} return result; +} + + +{{cppname}}Builder::{{cppname}}Builder() + : + fHasValueBitmask(0), + fSourceRef(), +{{#propertyarray}} {{property.cppmembername}}({{property.cppdefaultvalue}}){{^islast}}, +{{/islast}}{{/propertyarray}} +{ +} + + +{{cppname}}Builder::{{cppname}}Builder(const {{cppname}}Ref& source) + : + fHasValueBitmask(0), + fSourceRef(source), +{{#propertyarray}} {{property.cppmembername}}({{property.cppdefaultvalue}}){{^islast}}, +{{/islast}}{{/propertyarray}} +{ +} + + +{{cppname}}Builder::~{{cppname}}Builder() +{ +} + + +void +{{cppname}}Builder::_InitFromSource() +{ + if (fSourceRef.IsSet()) { + _Init(fSourceRef.Get()); + fSourceRef.Unset(); + } +} + + +void +{{cppname}}Builder::_Init(const {{cppname}}* source) +{ +{{#propertyarray}} if (source->IsSet{{property.cppname}}()) { + fHasValueBitmask |= {{cppobjectname}}::k{{property.cppname}}Bitmask; + {{property.cppmembername}} = source->{{property.cppname}}(); + } +{{/propertyarray}} +} + + +{{#propertyarray}}{{#property.isarray}}{{cppname}}Builder& +{{cppname}}Builder::AddTo{{property.cppname}}({{property.items.cpptype}} value) +{ +{{#property.items.isstring}} _InitFromSource(); + fHasValueBitmask |= {{cppobjectname}}::k{{property.cppname}}Bitmask; + {{property.cppmembername}}.push_back(value); +{{/property.items.isstring}}{{#property.items.isobject}} if (!value.IsSet()) { + debugger("value for [{{property.cppname}}] in class [{{cppname}}Builder}}] is unset"); + } else { + _InitFromSource(); + fHasValueBitmask |= {{cppobjectname}}::k{{property.cppname}}Bitmask; + {{property.cppmembername}}.push_back(value); + } +{{/property.items.isobject}} + return *this; +} +{{/property.isarray}} + +{{cppname}}Builder& +{{cppname}}Builder::With{{property.cppname}}({{property.cpptype}} value) +{ +{{#property.isobject}} if (!value->IsSet()) + return Without{{property.cppname}}(); +{{/property.isobject}} if (!fSourceRef.IsSet() || !fSourceRef->IsSet{{property.cppname}}() || fSourceRef->{{property.cppname}}() != value) { + _InitFromSource(); + fHasValueBitmask |= {{cppobjectname}}::k{{property.cppname}}Bitmask; + {{property.cppmembername}} = value; + } + return *this; +} + + +{{cppname}}Builder& +{{cppname}}Builder::Without{{property.cppname}}() +{ + if (!fSourceRef.IsSet() || fSourceRef->IsSet{{property.cppname}}()) { + _InitFromSource(); + fHasValueBitmask &= ~{{cppobjectname}}::k{{property.cppname}}Bitmask; + {{property.cppmembername}} = {{property.cppdefaultvalue}}; + } + return *this; +} +{{/propertyarray}} + +{{cppname}}Ref +{{cppname}}Builder::BuildRef() +{ + if (fSourceRef.IsSet()) + return fSourceRef; + + {{cppname}}* result = new {{cppname}}(); + +{{#propertyarray}} if (0 != (fHasValueBitmask & {{cppobjectname}}::k{{property.cppname}}Bitmask)) + result->_Set{{property.cppname}}({{property.cppmembername}}); +{{/propertyarray}} + return {{cppname}}Ref(result, true); +} + + +/*! Sets the builder as if it had no values set. Even if it was initialized + with an existing object, it will be reset with no values. +*/ +{{cppname}}Builder& +{{cppname}}Builder::Clear() +{ + fHasValueBitmask = 0; + fSourceRef.Unset(); + return *this; +}{{/objects}} \ No newline at end of file diff --git a/src/apps/haikudepot/build/scripts/template/parserheader.mustache b/src/apps/haikudepot/build/scripts/template/parserheader.mustache new file mode 100644 index 0000000000..ce509cc57e --- /dev/null +++ b/src/apps/haikudepot/build/scripts/template/parserheader.mustache @@ -0,0 +1,89 @@ +/* + * Generated Listener Object for {{outputfilename}} + */ + +#ifndef GEN_JSON_SCHEMA_PARSER__{{outputfilestemupper}}_H +#define GEN_JSON_SCHEMA_PARSER__{{outputfilestemupper}}_H + +#include + +#include "{{toplevel.cppname}}Model.h" + + +class _AbstractStacked{{toplevel.cppname}}JsonListener; + + +class AbstractMain{{toplevel.cppname}}JsonListener : public BJsonEventListener { +friend class _AbstractStacked{{toplevel.cppname}}JsonListener; +public: + AbstractMain{{toplevel.cppname}}JsonListener(); + virtual ~AbstractMain{{toplevel.cppname}}JsonListener(); + + void HandleError(status_t status, int32 line, const char* message); + void Complete(); + status_t ErrorStatus(); + +protected: + void SetStackedListener( + _AbstractStacked{{toplevel.cppname}}JsonListener* listener); + +protected: + status_t fErrorStatus; + _AbstractStacked{{toplevel.cppname}}JsonListener* fStackedListener; +}; + + +/*! Use this listener when you want to parse some JSON data that contains + just a single instance of {{toplevel.cppname}}. +*/ +class Single{{toplevel.cppname}}JsonListener + : public AbstractMain{{toplevel.cppname}}JsonListener { +friend class AbstractStacked{{toplevel.cppname}}JsonListener; +public: + Single{{toplevel.cppname}}JsonListener(); + virtual ~Single{{toplevel.cppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); + {{toplevel.cppname}}Ref Result(); + + static bool WithResultCallback(void*, const {{toplevel.cppname}}Ref value); + +private: + {{toplevel.cppname}}Ref fResult; +}; + + +/*! Concrete sub-classes of this class are able to respond to each + {{toplevel.cppname}} instance as + it is parsed from the bulk container. When the stream is + finished, the Complete() method is invoked. +*/ +class {{toplevel.cppname}}Listener { +public: + virtual bool Handle({{toplevel.cppname}}Ref item) = 0; + virtual void Complete() = 0; +}; + + +/*! Use this listener, together with an instance of a concrete + subclass of {{toplevel.cppname}}Listener + in order to parse the JSON data in a specific "bulk + container" format. Each time that an instance of + {{toplevel.cppname}} + is parsed, the instance item listener will be invoked. +*/ +class BulkContainer{{toplevel.cppname}}JsonListener + : public AbstractMain{{toplevel.cppname}}JsonListener { +friend class AbstractStacked{{toplevel.cppname}}JsonListener; +public: + BulkContainer{{toplevel.cppname}}JsonListener( + {{toplevel.cppname}}Listener* itemListener); + ~BulkContainer{{toplevel.cppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); + +private: + {{toplevel.cppname}}Listener* fItemListener; +}; + +#endif // GEN_JSON_SCHEMA_PARSER__{{outputfilestemupper}}_H \ No newline at end of file diff --git a/src/apps/haikudepot/build/scripts/template/parserimplementation.mustache b/src/apps/haikudepot/build/scripts/template/parserimplementation.mustache new file mode 100644 index 0000000000..d1ce269d8a --- /dev/null +++ b/src/apps/haikudepot/build/scripts/template/parserimplementation.mustache @@ -0,0 +1,916 @@ +/* + * Generated Listener Object for {{outputfilename}} + */ + +#include "{{toplevel.cppname}}JsonListener.h" + +#include + +#include + +// #pragma mark - private interfaces for the stacked listeners + + +/*! This class is the top level of the stacked listeners. The stack structure + is maintained in a linked list and sub-classes implement specific behaviors + depending where in the parse tree the stacked listener is working at. +*/ +class _AbstractStacked{{toplevel.cppname}}JsonListener : public BJsonEventListener { +public: + _AbstractStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent); + ~_AbstractStacked{{toplevel.cppname}}JsonListener(); + + void HandleError(status_t status, int32 line, const char* message); + void Complete(); + + status_t ErrorStatus(); + + _AbstractStacked{{toplevel.cppname}}JsonListener* Parent(); + + virtual bool WillPop(); + + virtual void Clear(); + +protected: + AbstractMain{{toplevel.cppname}}JsonListener* fMainListener; + + virtual bool Pop(); + + void Push(_AbstractStacked{{toplevel.cppname}}JsonListener* stackedListener); + +private: + _AbstractStacked{{toplevel.cppname}}JsonListener* fParent; +}; + + +/*! This implementation of the listener will consume data from the array and + discard it. +*/ +class _GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener : public _AbstractStacked{{toplevel.cppname}}JsonListener { +public: + _GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent); + ~_GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); +}; + + +class _GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener : public _AbstractStacked{{toplevel.cppname}}JsonListener { +public: + _GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent); + ~_GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); +}; + + +/*! Sometimes attributes of objects are able to be arrays of strings. This + listener will parse and return the array of strings. +*/ + +class _StringListStacked{{toplevel.cppname}}JsonListener : public _AbstractStacked{{toplevel.cppname}}JsonListener { +public: + _StringListStacked{{toplevel.cppname}}JsonListener( + bool (*callback)(void* context, const std::vector& values), + void* callbackContext, + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent); + ~_StringListStacked{{toplevel.cppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); + + void Clear(); + +protected: + bool Pop(); + +protected: + std::vector fAccumulator; + bool (*fCallback)(void* context, const std::vector& values); + void* fCallbackContext; +}; +{{#allobjs}} + +class _{{cppname}}Stacked{{toplevelcppname}}JsonListener : public _AbstractStacked{{toplevelcppname}}JsonListener { +public: + _{{cppname}}Stacked{{toplevelcppname}}JsonListener( + bool (*callback)(void* context, const {{cppname}}Ref ref), + void* callbackContext, + AbstractMain{{toplevelcppname}}JsonListener* mainListener, + _AbstractStacked{{toplevelcppname}}JsonListener* parent); + ~_{{cppname}}Stacked{{toplevelcppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); + + void Clear(); + +{{#propertyarray}}{{#property.isobject}} static bool With{{property.cppname}}Callback(void* context, const {{property.cpptype}} value); +{{/property.isobject}}{{#property.isarray}} static bool With{{property.cppname}}Callback(void* context, const {{property.cpptype}}& value); +{{/property.isarray}}{{/propertyarray}} + +protected: + bool Pop(); + +protected: + {{cppname}}Builder fBuilder; + uint16 fNextItemBitmask; + bool (*fCallback)(void* context, const {{cppname}}Ref ref); + void* fCallbackContext; +}; + +class _{{cppname}}ListStacked{{toplevelcppname}}JsonListener : public _AbstractStacked{{toplevelcppname}}JsonListener { +public: + _{{cppname}}ListStacked{{toplevelcppname}}JsonListener( + bool (*callback)(void* context, const std::vector<{{cppname}}Ref>& values), + void* callbackContext, + AbstractMain{{toplevelcppname}}JsonListener* mainListener, + _AbstractStacked{{toplevelcppname}}JsonListener* parent); + ~_{{cppname}}ListStacked{{toplevelcppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); + + void Clear(); + + static bool AddItemCallback(void* target, const {{cppname}}Ref value); + +protected: + bool Pop(); + +private: + std::vector<{{cppname}}Ref> fAccumulator; + bool (*fCallback)(void* context, const std::vector<{{cppname}}Ref>& values); + void* fCallbackContext; +}; +{{/allobjs}} + + +class _BulkContainerStacked{{toplevel.cppname}}JsonListener : public _AbstractStacked{{toplevel.cppname}}JsonListener { +public: + _BulkContainerStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent, + {{toplevel.cppname}}Listener* itemListener); + ~_BulkContainerStacked{{toplevel.cppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); + + void Clear(); + +private: + bool fNextItemIsItems; + {{toplevel.cppname}}Listener* fItemListener; +}; + + +class _BulkContainerItemsStacked{{toplevel.cppname}}JsonListener : public _AbstractStacked{{toplevel.cppname}}JsonListener { +public: + _BulkContainerItemsStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent, + {{toplevel.cppname}}Listener* itemListener); + ~_BulkContainerItemsStacked{{toplevel.cppname}}JsonListener(); + + bool Handle(const BJsonEvent& event); + bool WillPop(); + + static bool NotifyListenerCallback(void* context, const {{toplevel.cppname}}Ref value); + +private: + {{toplevel.cppname}}Listener* fItemListener; +}; + + +// #pragma mark - implementations for the stacked listeners + + +_AbstractStacked{{toplevel.cppname}}JsonListener::_AbstractStacked{{toplevel.cppname}}JsonListener ( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent) +{ + fMainListener = mainListener; + fParent = parent; +} + + +_AbstractStacked{{toplevel.cppname}}JsonListener::~_AbstractStacked{{toplevel.cppname}}JsonListener() +{ +} + + +void +_AbstractStacked{{toplevel.cppname}}JsonListener::HandleError(status_t status, int32 line, const char* message) +{ + fMainListener->HandleError(status, line, message); +} + + +void +_AbstractStacked{{toplevel.cppname}}JsonListener::Complete() +{ + fMainListener->Complete(); +} + + +status_t +_AbstractStacked{{toplevel.cppname}}JsonListener::ErrorStatus() +{ + return fMainListener->ErrorStatus(); +} + + +_AbstractStacked{{toplevel.cppname}}JsonListener* +_AbstractStacked{{toplevel.cppname}}JsonListener::Parent() +{ + return fParent; +} + + +void +_AbstractStacked{{toplevel.cppname}}JsonListener::Push(_AbstractStacked{{toplevel.cppname}}JsonListener* stackedListener) +{ + fMainListener->SetStackedListener(stackedListener); +} + + +bool +_AbstractStacked{{toplevel.cppname}}JsonListener::WillPop() +{ + return true; +} + + +bool +_AbstractStacked{{toplevel.cppname}}JsonListener::Pop() +{ + bool result = WillPop(); + fMainListener->SetStackedListener(fParent); + return result; +} + + +void +_AbstractStacked{{toplevel.cppname}}JsonListener::Clear() +{ +} + + +_GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener::_GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent) + : + _AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent) +{ +} + +_GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener::~_GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener() +{ +} + +bool +_GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) +{ + switch (event.EventType()) { + case B_JSON_OBJECT_NAME: + case B_JSON_NUMBER: + case B_JSON_STRING: + case B_JSON_TRUE: + case B_JSON_FALSE: + case B_JSON_NULL: + // ignore + break; + case B_JSON_OBJECT_START: + Push(new _GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); + break; + case B_JSON_ARRAY_START: + Push(new _GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); + break; + case B_JSON_ARRAY_END: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected end of array"); + break; + case B_JSON_OBJECT_END: + { + bool status = Pop() && (ErrorStatus() == B_OK); + delete this; + return status; + } + } + + return ErrorStatus() == B_OK; +} + +_GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener::_GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent) + : + _AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent) +{ +} + +_GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener::~_GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener() +{ +} + +bool +_GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) +{ + switch (event.EventType()) { + case B_JSON_OBJECT_NAME: + case B_JSON_NUMBER: + case B_JSON_STRING: + case B_JSON_TRUE: + case B_JSON_FALSE: + case B_JSON_NULL: + // ignore + break; + case B_JSON_OBJECT_START: + Push(new _GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); + break; + case B_JSON_ARRAY_START: + Push(new _GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); + break; + case B_JSON_OBJECT_END: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected end of object"); + break; + case B_JSON_ARRAY_END: + { + bool status = Pop() && (ErrorStatus() == B_OK); + delete this; + return status; + } + } + + + return ErrorStatus() == B_OK; +} + +_StringListStacked{{toplevel.cppname}}JsonListener::_StringListStacked{{toplevel.cppname}}JsonListener( + bool (*callback)(void* context, const std::vector& values), + void* callbackContext, + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, + _AbstractStacked{{toplevel.cppname}}JsonListener* parent) + : + _AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent), + fAccumulator(), + fCallback(callback), + fCallbackContext(callbackContext) +{ +} + + +_StringListStacked{{toplevel.cppname}}JsonListener::~_StringListStacked{{toplevel.cppname}}JsonListener() +{ +} + + +bool +_StringListStacked{{toplevel.cppname}}JsonListener::Pop() +{ + bool callbackResult = fCallback(fCallbackContext, fAccumulator); + return _AbstractStacked{{toplevel.cppname}}JsonListener::Pop() && callbackResult; +} + + +void +_StringListStacked{{toplevel.cppname}}JsonListener::Clear() +{ + _AbstractStacked{{toplevel.cppname}}JsonListener::Clear(); + fAccumulator.clear(); +} + + +bool +_StringListStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) +{ + switch (event.EventType()) { + case B_JSON_ARRAY_END: + { + bool status = Pop() && (ErrorStatus() == B_OK); + delete this; + return status; + } + case B_JSON_STRING: + { + fAccumulator.push_back(event.Content()); + break; + } + default: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, + "illegal state - unexpected json event parsing a string array"); + break; + } + + return ErrorStatus() == B_OK; +} + +{{#allobjs}} +_{{cppname}}Stacked{{toplevelcppname}}JsonListener::_{{cppname}}Stacked{{toplevelcppname}}JsonListener( + bool (*callback)(void* context, const {{cppname}}Ref ref), + void* callbackContext, + AbstractMain{{toplevelcppname}}JsonListener* mainListener, + _AbstractStacked{{toplevelcppname}}JsonListener* parent) + : + _AbstractStacked{{toplevelcppname}}JsonListener(mainListener, parent), + fBuilder(), + fCallback(callback), + fCallbackContext(callbackContext) +{ +} + + +_{{cppname}}Stacked{{toplevelcppname}}JsonListener::~_{{cppname}}Stacked{{toplevelcppname}}JsonListener() +{ +} + + +bool +_{{cppname}}Stacked{{toplevelcppname}}JsonListener::Pop() +{ + bool callbackResult = fCallback(fCallbackContext, fBuilder.BuildRef()); + return _AbstractStacked{{toplevelcppname}}JsonListener::Pop() && callbackResult; +} +{{#propertyarray}}{{#property.isobject}} +/*static*/ bool +_{{cppname}}Stacked{{toplevelcppname}}JsonListener::With{{property.cppname}}Callback(void* context, const {{property.cpptype}} value) +{{/property.isobject}}{{#property.isarray}}/*static*/ bool +_{{cppname}}Stacked{{toplevelcppname}}JsonListener::With{{property.cppname}}Callback(void* context, const {{property.cpptype}}& value) +{{/property.isarray}}{{#property.isobjectorarray}}{ + {{cppname}}Builder* builder = static_cast<{{cppname}}Builder*>(context); + builder->With{{property.cppname}}(value); + return true; +} +{{/property.isobjectorarray}}{{/propertyarray}} + + +void +_{{cppname}}Stacked{{toplevelcppname}}JsonListener::Clear() +{ + _AbstractStacked{{toplevel.cppname}}JsonListener::Clear(); + fBuilder.Clear(); + fNextItemBitmask = 0; +} + + +bool +_{{cppname}}Stacked{{toplevelcppname}}JsonListener::Handle(const BJsonEvent& event) +{ + switch (event.EventType()) { + case B_JSON_ARRAY_END: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected start of array"); + break; + case B_JSON_OBJECT_NAME: + fNextItemBitmask = 0; +{{#propertyarray}} if (0 == strcmp(event.Content(), "{{name}}")) + fNextItemBitmask = {{cppobjectname}}::k{{property.cppname}}Bitmask; +{{/propertyarray}} + break; + case B_JSON_OBJECT_END: + { + bool status = Pop() && (ErrorStatus() == B_OK); + delete this; + return status; + } + case B_JSON_STRING: + { +{{#propertyarray}}{{#property.isstring}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) + fBuilder.With{{property.cppname}}(event.Content()); +{{/property.isstring}}{{/propertyarray}} + fNextItemBitmask = 0; + break; + } + case B_JSON_TRUE: +{{#propertyarray}}{{#property.isboolean}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) + fBuilder.With{{property.cppname}}(true); +{{/property.isboolean}}{{/propertyarray}} + fNextItemBitmask = 0; + break; + case B_JSON_FALSE: +{{#propertyarray}}{{#property.isboolean}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) + fBuilder.With{{property.cppname}}(false); +{{/property.isboolean}} +{{/propertyarray}} + fNextItemBitmask = 0; + break; + case B_JSON_NULL: + { +{{#propertyarray}}{{^property.isarray}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) + fBuilder.Without{{property.cppname}}(); +{{/property.isarray}}{{/propertyarray}} + fNextItemBitmask = 0; + break; + } + case B_JSON_NUMBER: + { +{{#propertyarray}}{{#property.isinteger}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) + fBuilder.With{{property.cppname}}(event.ContentInteger()); +{{/property.isinteger}} +{{#property.isnumber}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) + fBuilder.With{{property.cppname}}(event.ContentDouble()); +{{/property.isnumber}} +{{/propertyarray}} + fNextItemBitmask = 0; + break; + } + case B_JSON_OBJECT_START: + { +{{#propertyarray}}{{#property.isobject}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) { + _GeneralNoOpObjectStacked{{toplevelcppname}}JsonListener* nextListener + = new _GeneralNoOpObjectStacked{{toplevelcppname}}JsonListener( + _{{cppname}}Stacked{{toplevelcppname}}JsonListener::With{{property.cppname}}, + &fBuilder, + fMainListener, + this); + Push(nextListener); + fNextItemBitmask = 0; + break; + } +{{/property.isobject}}{{/propertyarray}} + // a general consumer that will clear any spurious data that was not + // part of the schema. This is important for schema evolution. + _GeneralNoOpObjectStacked{{toplevelcppname}}JsonListener* nextListener + = new _GeneralNoOpObjectStacked{{toplevelcppname}}JsonListener(fMainListener, this); + Push(nextListener); + fNextItemBitmask = 0; + break; + } + case B_JSON_ARRAY_START: + { +{{#propertyarray}}{{#property.isarray}} if (fNextItemBitmask == {{cppobjectname}}::k{{property.cppname}}Bitmask) { + {{#property.items.isstring}} + _StringListStacked{{toplevelcppname}}JsonListener* nextListener = new _StringListStacked{{toplevelcppname}}JsonListener( + _{{cppname}}Stacked{{toplevelcppname}}JsonListener::With{{property.cppname}}Callback, + &fBuilder, + fMainListener, + this); + {{/property.items.isstring}}{{^property.items.isstring}} + _{{property.items.cppname}}ListStacked{{toplevelcppname}}JsonListener* nextListener = + new _{{property.items.cppname}}ListStacked{{toplevelcppname}}JsonListener( + _{{cppname}}Stacked{{toplevelcppname}}JsonListener::With{{property.cppname}}Callback, + &fBuilder, + fMainListener, + this); + {{/property.items.isstring}} + Push(nextListener); + fNextItemBitmask = 0; + break; + } +{{/property.isarray}}{{/propertyarray}} + // a general consumer that will clear any spurious data that was not + // part of the schema. This is important for schema evolution. + _AbstractStacked{{toplevelcppname}}JsonListener* nextListener + = new _GeneralNoOpArrayStacked{{toplevelcppname}}JsonListener(fMainListener, this); + Push(nextListener); + fNextItemBitmask = 0; + break; + } + } + + return ErrorStatus() == B_OK; +} + + +_{{cppname}}ListStacked{{toplevelcppname}}JsonListener::_{{cppname}}ListStacked{{toplevelcppname}}JsonListener( + bool (*callback)(void* context, const std::vector<{{cppname}}Ref>& values), + void* callbackContext, + AbstractMain{{toplevelcppname}}JsonListener* mainListener, + _AbstractStacked{{toplevelcppname}}JsonListener* parent) + : + _AbstractStacked{{toplevelcppname}}JsonListener(mainListener, parent), + fAccumulator(), + fCallback(callback), + fCallbackContext(callbackContext) +{ +} + + +_{{cppname}}ListStacked{{toplevelcppname}}JsonListener::~_{{cppname}}ListStacked{{toplevelcppname}}JsonListener() +{ +} + + +bool +_{{cppname}}ListStacked{{toplevelcppname}}JsonListener::Pop() +{ + bool callbackResult = fCallback(fCallbackContext, fAccumulator); + return _AbstractStacked{{toplevelcppname}}JsonListener::Pop() && callbackResult; +} + + +/*static*/ bool +_{{cppname}}ListStacked{{toplevelcppname}}JsonListener::AddItemCallback(void* context, const {{cppname}}Ref value) +{ + std::vector<{{cppname}}Ref>* accumulator = static_cast*>(context); + accumulator->push_back(value); + return true; +} + + +bool +_{{cppname}}ListStacked{{toplevelcppname}}JsonListener::Handle(const BJsonEvent& event) +{ + switch (event.EventType()) { + case B_JSON_ARRAY_END: + { + bool status = Pop() && (ErrorStatus() == B_OK); + delete this; + return status; + } + case B_JSON_OBJECT_START: + { + _{{cppname}}Stacked{{toplevelcppname}}JsonListener* nextListener = + new _{{cppname}}Stacked{{toplevelcppname}}JsonListener( + AddItemCallback, + &fAccumulator, + fMainListener, + this); + Push(nextListener); + break; + } + default: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, + "illegal state - unexpected json event parsing an array of {{cppname}}"); + break; + } + + return ErrorStatus() == B_OK; +} + + +void +_{{cppname}}ListStacked{{toplevelcppname}}JsonListener::Clear() +{ + _AbstractStacked{{toplevel.cppname}}JsonListener::Clear(); + fAccumulator.clear(); +} +{{/allobjs}} + +_BulkContainerStacked{{toplevel.cppname}}JsonListener::_BulkContainerStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, _AbstractStacked{{toplevel.cppname}}JsonListener* parent, + {{toplevel.cppname}}Listener* itemListener) +: +_AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent) +{ + fItemListener = itemListener; +} + + +_BulkContainerStacked{{toplevel.cppname}}JsonListener::~_BulkContainerStacked{{toplevel.cppname}}JsonListener() +{ +} + + +bool +_BulkContainerStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) +{ + switch (event.EventType()) { + case B_JSON_ARRAY_END: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected end of array"); + break; + case B_JSON_OBJECT_NAME: + fNextItemIsItems = (0 == strcmp(event.Content(), "items")); + break; + case B_JSON_OBJECT_START: + Push(new _GeneralNoOpObjectStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); + fNextItemIsItems = false; + break; + case B_JSON_ARRAY_START: + if (fNextItemIsItems) + Push(new _BulkContainerItemsStacked{{toplevel.cppname}}JsonListener(fMainListener, this, fItemListener)); + else + Push(new _GeneralNoOpArrayStacked{{toplevel.cppname}}JsonListener(fMainListener, this)); + break; + case B_JSON_OBJECT_END: + { + bool status = Pop() && (ErrorStatus() == B_OK); + delete this; + return status; + } + default: + // ignore any other fields + fNextItemIsItems = false; + break; + } + + return ErrorStatus() == B_OK; +} + + +void +_BulkContainerStacked{{toplevel.cppname}}JsonListener::Clear() +{ + _AbstractStacked{{toplevel.cppname}}JsonListener::Clear(); + fNextItemIsItems = false; +} + + +_BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::_BulkContainerItemsStacked{{toplevel.cppname}}JsonListener( + AbstractMain{{toplevel.cppname}}JsonListener* mainListener, _AbstractStacked{{toplevel.cppname}}JsonListener* parent, + {{toplevel.cppname}}Listener* itemListener) + : + _AbstractStacked{{toplevel.cppname}}JsonListener(mainListener, parent), + fItemListener(itemListener) +{ +} + + +_BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::~_BulkContainerItemsStacked{{toplevel.cppname}}JsonListener() +{ +} + + +bool +_BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) +{ + switch (event.EventType()) { + case B_JSON_OBJECT_START: + Push(new _{{toplevel.cppname}}Stacked{{toplevel.cppname}}JsonListener( + _BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::NotifyListenerCallback, + fItemListener, + fMainListener, + this + )); + break; + case B_JSON_ARRAY_END: + { + bool status = Pop() && (ErrorStatus() == B_OK); + delete this; + return status; + } + default: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected json event"); + break; + } + + return ErrorStatus() == B_OK; +} + + +/*static*/ bool +_BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::NotifyListenerCallback(void* context, const {{toplevel.cppname}}Ref value) +{ + {{toplevel.cppname}}Listener* listener = static_cast<{{toplevel.cppname}}Listener*>(context); + return listener->Handle(value); +} + + +bool +_BulkContainerItemsStacked{{toplevel.cppname}}JsonListener::WillPop() +{ + fItemListener->Complete(); + return true; +} + + +// #pragma mark - implementations for the main listeners + + +AbstractMain{{toplevel.cppname}}JsonListener::AbstractMain{{toplevel.cppname}}JsonListener() +{ + fStackedListener = NULL; + fErrorStatus = B_OK; +} + + +AbstractMain{{toplevel.cppname}}JsonListener::~AbstractMain{{toplevel.cppname}}JsonListener() +{ +} + + +void +AbstractMain{{toplevel.cppname}}JsonListener::HandleError(status_t status, int32 line, const char* message) +{ + if (message != NULL) { + fprintf(stderr, "an error has arisen processing json for '{{toplevel.cppname}}'; %s\\n", message); + } else { + fprintf(stderr, "an error has arisen processing json for '{{toplevel.cppname}}'\\n"); + } + fErrorStatus = status; +} + + +void +AbstractMain{{toplevel.cppname}}JsonListener::Complete() +{ +} + + +status_t +AbstractMain{{toplevel.cppname}}JsonListener::ErrorStatus() +{ + return fErrorStatus; +} + +void +AbstractMain{{toplevel.cppname}}JsonListener::SetStackedListener( + _AbstractStacked{{toplevel.cppname}}JsonListener* stackedListener) +{ + fStackedListener = stackedListener; +} + + +Single{{toplevel.cppname}}JsonListener::Single{{toplevel.cppname}}JsonListener() + : + AbstractMain{{toplevel.cppname}}JsonListener(), + fResult() +{ +} + + +Single{{toplevel.cppname}}JsonListener::~Single{{toplevel.cppname}}JsonListener() +{ +} + + +bool +Single{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) +{ + if (fErrorStatus != B_OK) + return false; + + if (fStackedListener != NULL) + return fStackedListener->Handle(event); + + switch (event.EventType()) { + case B_JSON_OBJECT_START: + { + _{{toplevel.cppname}}Stacked{{toplevel.cppname}}JsonListener* nextListener + = new _{{toplevel.cppname}}Stacked{{toplevel.cppname}}JsonListener( + Single{{toplevel.cppname}}JsonListener::WithResultCallback, + &fResult, + this, + NULL); + SetStackedListener(nextListener); + break; + } + default: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, + "illegal state - unexpected json event parsing top level for {{toplevel.cppname}}"); + break; + } + + return ErrorStatus() == B_OK; +} + + +/*static*/ bool +Single{{toplevel.cppname}}JsonListener::WithResultCallback(void* context, const {{toplevel.cppname}}Ref value) +{ + {{toplevel.cppname}}Ref* ref = static_cast<{{toplevel.cppname}}Ref*>(context); + if (value.IsSet()) + ref->SetTo(value.Get(), false); + else + ref->Unset(); + return true; +} + + +{{toplevel.cppname}}Ref +Single{{toplevel.cppname}}JsonListener::Result() +{ + return fResult; +} + + +BulkContainer{{toplevel.cppname}}JsonListener::BulkContainer{{toplevel.cppname}}JsonListener( + {{toplevel.cppname}}Listener* itemListener) : AbstractMain{{toplevel.cppname}}JsonListener() +{ + fItemListener = itemListener; +} + + +BulkContainer{{toplevel.cppname}}JsonListener::~BulkContainer{{toplevel.cppname}}JsonListener() +{ +} + + +bool +BulkContainer{{toplevel.cppname}}JsonListener::Handle(const BJsonEvent& event) +{ + if (fErrorStatus != B_OK) + return false; + + if (fStackedListener != NULL) + return fStackedListener->Handle(event); + + switch (event.EventType()) { + case B_JSON_OBJECT_START: + { + _BulkContainerStacked{{toplevel.cppname}}JsonListener* nextListener = + new _BulkContainerStacked{{toplevel.cppname}}JsonListener(this, NULL, fItemListener); + SetStackedListener(nextListener); + return true; + break; + } + default: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, + "illegal state - unexpected json event parsing top level for _BulkContainerStacked{{toplevel.cppname}}JsonListener"); + break; + } + + return ErrorStatus() == B_OK; +} \ No newline at end of file diff --git a/src/apps/haikudepot/server/LocalRepositoryUpdateProcess.cpp b/src/apps/haikudepot/server/LocalRepositoryUpdateProcess.cpp index 14c75d2130..cd368584ef 100644 --- a/src/apps/haikudepot/server/LocalRepositoryUpdateProcess.cpp +++ b/src/apps/haikudepot/server/LocalRepositoryUpdateProcess.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020, Andrew Lindesay . + * Copyright 2018-2026, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -132,6 +132,8 @@ LocalRepositoryUpdateProcess::_RunForRepositoryName(const BString& repoName, BPackageKit::BContext& context, BPackageKit::BPackageRoster& roster, BPackageKit::BRepositoryCache* cache) { + HDINFO("[%s] will update local repo [%s] cache", Name(), repoName.String()); + status_t result = B_ERROR; BRepositoryConfig repoConfig; result = roster.GetRepositoryConfig(repoName, &repoConfig); @@ -140,8 +142,7 @@ LocalRepositoryUpdateProcess::_RunForRepositoryName(const BString& repoName, try { BRefreshRepositoryRequest refreshRequest(context, repoConfig); result = refreshRequest.Process(); - HDINFO("[%s] did update local repo [%s] cache", Name(), - repoName.String()); + HDINFO("[%s] did update local repo [%s] cache", Name(), repoName.String()); result = B_OK; } catch (BFatalErrorException& ex) { _NotifyError(ex.Message(), ex.Details()); diff --git a/src/apps/haikudepot/server/ServerIconExportUpdateProcess.h b/src/apps/haikudepot/server/ServerIconExportUpdateProcess.h index bffb83448e..07efa82bfe 100644 --- a/src/apps/haikudepot/server/ServerIconExportUpdateProcess.h +++ b/src/apps/haikudepot/server/ServerIconExportUpdateProcess.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2025, Andrew Lindesay . + * Copyright 2017-2026, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef SERVER_ICON_EXPORT_UPDATE_PROCESS_H @@ -15,9 +15,6 @@ #include "Model.h" -class DumpExportPkg; - - class ServerIconExportUpdateProcess : public AbstractSingleFileServerProcess { public: diff --git a/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp b/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp index 504d709f00..2e8db192ea 100644 --- a/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp +++ b/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2025, Andrew Lindesay . + * Copyright 2017-2026, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -17,11 +17,8 @@ #include #include -#include "DumpExportPkg.h" -#include "DumpExportPkgCategory.h" #include "DumpExportPkgJsonListener.h" -#include "DumpExportPkgScreenshot.h" -#include "DumpExportPkgVersion.h" +#include "DumpExportPkgModel.h" #include "HaikuDepotConstants.h" #include "Logger.h" #include "PackageUtils.h" @@ -49,7 +46,7 @@ public: PackageFillingPkgListener(Model *model, Stoppable* stoppable); virtual ~PackageFillingPkgListener(); - virtual bool Handle(DumpExportPkg* item); + virtual bool Handle(DumpExportPkgRef item); virtual void Complete(); uint32 Count(); @@ -153,19 +150,19 @@ PackageFillingPkgListener::_CreateUpdatePackage(const PackageInfoRef& package, DumpExportPkgVersion* pkgVersion = pkg->PkgVersionsItemAt(0); - if (!pkgVersion->TitleIsNull()) - localizedTextBuilder.WithTitle(*(pkgVersion->Title())); + if (pkgVersion->IsSetTitle()) + localizedTextBuilder.WithTitle(pkgVersion->Title()); - if (!pkgVersion->SummaryIsNull()) - localizedTextBuilder.WithSummary(*(pkgVersion->Summary())); + if (pkgVersion->IsSetSummary()) + localizedTextBuilder.WithSummary(pkgVersion->Summary()); - if (!pkgVersion->DescriptionIsNull()) - localizedTextBuilder.WithDescription(*(pkgVersion->Description())); + if (pkgVersion->IsSetDescription()) + localizedTextBuilder.WithDescription(pkgVersion->Description()); - if (!pkgVersion->PayloadLengthIsNull()) + if (pkgVersion->IsSetPayloadLength()) localInfoBuilder.WithSize(static_cast(pkgVersion->PayloadLength())); - if (!pkgVersion->CreateTimestampIsNull()) { + if (pkgVersion->IsSetCreateTimestamp()) { PackageVersionRef versionRef = PackageUtils::Version(package); if (versionRef.IsSet()) { @@ -181,7 +178,7 @@ PackageFillingPkgListener::_CreateUpdatePackage(const PackageInfoRef& package, } } - if (!pkg->DerivedRatingIsNull()) { + if (pkg->IsSetDerivedRating()) { // TODO; unify the naming here! userRatingBuilder.WithSummary(UserRatingSummaryBuilder() .WithAverageRating(pkg->DerivedRating()) @@ -192,22 +189,22 @@ PackageFillingPkgListener::_CreateUpdatePackage(const PackageInfoRef& package, int32 countPkgCategories = pkg->CountPkgCategories(); for (i = 0; i < countPkgCategories; i++) { - BString* categoryCode = pkg->PkgCategoriesItemAt(i)->Code(); - CategoryRef category = fCategories[*categoryCode]; + BString categoryCode = pkg->PkgCategoriesItemAt(i)->Code(); + CategoryRef category = fCategories[categoryCode]; if (!category.IsSet()) - HDERROR("unable to find the category for [%s]", categoryCode->String()); + HDERROR("unable to find the category for [%s]", categoryCode.String()); else classificationInfoBuilder.AddCategory(category); } - if (!pkg->ProminenceOrderingIsNull()) + if (pkg->IsSetProminenceOrdering()) classificationInfoBuilder.WithProminence(static_cast(pkg->ProminenceOrdering())); - if (!pkg->IsDesktopIsNull()) + if (pkg->IsSetIsDesktop()) classificationInfoBuilder.WithIsDesktop(pkg->IsDesktop()); - if (!pkg->IsNativeDesktopIsNull()) + if (pkg->IsSetIsNativeDesktop()) classificationInfoBuilder.WithIsNativeDesktop(pkg->IsNativeDesktop()); int32 countPkgScreenshots = pkg->CountPkgScreenshots(); @@ -232,7 +229,7 @@ PackageFillingPkgListener::_CreateUpdatePackage(const PackageInfoRef& package, PackageFillingPkgListener::_CreateScreenshot(DumpExportPkgScreenshot* screenshot) { return ScreenshotInfoRef( - new ScreenshotInfo(*(screenshot->Code()), static_cast(screenshot->Width()), + new ScreenshotInfo(screenshot->Code(), static_cast(screenshot->Width()), static_cast(screenshot->Height()), static_cast(screenshot->Length())), true); } @@ -246,14 +243,14 @@ PackageFillingPkgListener::Count() bool -PackageFillingPkgListener::Handle(DumpExportPkg* pkg) +PackageFillingPkgListener::Handle(DumpExportPkgRef pkg) { - const BString packageName = *(pkg->Name()); + const BString packageName = pkg->Name(); PackageInfoRef package = _PackageForName(packageName); if (package.IsSet()) { fUpdatedPackages.push_back(_CreateUpdatePackage(package, pkg)); - HDTRACE("did populate data for [%s]", pkg->Name()->String()); + HDTRACE("did populate data for [%s]", packageName.String()); fCount++; } else { HDINFO("[PackageFillingPkgListener] unable to find the pkg [%s]", packageName.String()); diff --git a/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp index e888b0402b..bdbed59472 100644 --- a/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp +++ b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025, Andrew Lindesay . + * Copyright 2019-2026, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -11,17 +11,12 @@ #include #include -#include #include #include #include -#include "DumpExportReference.h" -#include "DumpExportReferenceCountry.h" #include "DumpExportReferenceJsonListener.h" -#include "DumpExportReferenceNaturalLanguage.h" -#include "DumpExportReferencePkgCategory.h" -#include "DumpExportReferenceUserRatingStability.h" +#include "DumpExportReferenceModel.h" #include "LocaleUtils.h" #include "Logger.h" #include "ServerSettings.h" @@ -80,6 +75,7 @@ status_t ServerReferenceDataUpdateProcess::ProcessLocalData() { SingleDumpExportReferenceJsonListener* listener = new SingleDumpExportReferenceJsonListener(); + ObjectDeleter listenerDeleter(listener); BPath localPath; status_t result = GetLocalPath(localPath); @@ -97,12 +93,12 @@ ServerReferenceDataUpdateProcess::ProcessLocalData() if (result != B_OK) return result; - return _ProcessData(listener->Target()); + return _ProcessData(listener->Result()); } status_t -ServerReferenceDataUpdateProcess::_ProcessData(DumpExportReference* data) +ServerReferenceDataUpdateProcess::_ProcessData(DumpExportReferenceRef data) { status_t result = B_OK; if (result == B_OK) @@ -116,7 +112,7 @@ ServerReferenceDataUpdateProcess::_ProcessData(DumpExportReference* data) status_t -ServerReferenceDataUpdateProcess::_ProcessNaturalLanguages(DumpExportReference* data) +ServerReferenceDataUpdateProcess::_ProcessNaturalLanguages(DumpExportReferenceRef data) { HDINFO("[%s] will populate from %" B_PRId32 " possible natural languages", Name(), data->CountNaturalLanguages()); @@ -126,7 +122,7 @@ ServerReferenceDataUpdateProcess::_ProcessNaturalLanguages(DumpExportReference* for (int32 i = data->CountNaturalLanguages() - 1; i > 0; i--) { DumpExportReferenceNaturalLanguage* naturalLanguage = data->NaturalLanguagesItemAt(i); - Language* language = new Language(*(naturalLanguage->Code()), *(naturalLanguage->Name()), + Language* language = new Language(naturalLanguage->Code(), naturalLanguage->Name(), naturalLanguage->IsPopular()); languages.push_back(LanguageRef(language, true)); count++; @@ -142,7 +138,7 @@ ServerReferenceDataUpdateProcess::_ProcessNaturalLanguages(DumpExportReference* status_t -ServerReferenceDataUpdateProcess::_ProcessPkgCategories(DumpExportReference* data) +ServerReferenceDataUpdateProcess::_ProcessPkgCategories(DumpExportReferenceRef data) { HDINFO("[%s] will populate %" B_PRId32 " pkg categories", Name(), data->CountPkgCategories()); @@ -151,7 +147,7 @@ ServerReferenceDataUpdateProcess::_ProcessPkgCategories(DumpExportReference* dat for (int32 i = 0; i < data->CountPkgCategories(); i++) { DumpExportReferencePkgCategory* pkgCategory = data->PkgCategoriesItemAt(i); assembledCategories.push_back( - CategoryRef(new PackageCategory(*(pkgCategory->Code()), *(pkgCategory->Name())), true)); + CategoryRef(new PackageCategory(pkgCategory->Code(), pkgCategory->Name()), true)); } fModel->SetCategories(assembledCategories); @@ -161,7 +157,7 @@ ServerReferenceDataUpdateProcess::_ProcessPkgCategories(DumpExportReference* dat status_t -ServerReferenceDataUpdateProcess::_ProcessRatingStabilities(DumpExportReference* data) +ServerReferenceDataUpdateProcess::_ProcessRatingStabilities(DumpExportReferenceRef data) { HDINFO("[%s] will populate %" B_PRId32 " rating stabilities", Name(), data->CountUserRatingStabilities()); @@ -172,8 +168,8 @@ ServerReferenceDataUpdateProcess::_ProcessRatingStabilities(DumpExportReference* DumpExportReferenceUserRatingStability* ratingStability = data->UserRatingStabilitiesItemAt(i); assembledRatingStabilities.push_back( - RatingStabilityRef(new RatingStability(*(ratingStability->Code()), - *(ratingStability->Name()), ratingStability->Ordering()), + RatingStabilityRef(new RatingStability(ratingStability->Code(), ratingStability->Name(), + ratingStability->Ordering()), true)); } diff --git a/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h index a969e9d8cb..59a69b6a1d 100644 --- a/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h +++ b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020, Andrew Lindesay . + * Copyright 2019-2026, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef SERVER_REFERENCE_DATA_UPDATE_PROCESS_H @@ -13,6 +13,7 @@ #include #include +#include "DumpExportReferenceModel.h" #include "Model.h" #include "PackageInfo.h" @@ -20,7 +21,6 @@ #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "ServerReferenceDataUpdateProcess" -class DumpExportReference; class ServerReferenceDataUpdateProcess : public AbstractSingleFileServerProcess { @@ -43,13 +43,10 @@ protected: status_t GetLocalPath(BPath& path) const; private: - status_t _ProcessData(DumpExportReference* data); - status_t _ProcessNaturalLanguages( - DumpExportReference* data); - status_t _ProcessPkgCategories( - DumpExportReference* data); - status_t _ProcessRatingStabilities( - DumpExportReference* data); + status_t _ProcessData(DumpExportReferenceRef data); + status_t _ProcessNaturalLanguages(DumpExportReferenceRef data); + status_t _ProcessPkgCategories(DumpExportReferenceRef data); + status_t _ProcessRatingStabilities(DumpExportReferenceRef data); private: Model* fModel; diff --git a/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp b/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp index e3b22aacee..416a745548 100644 --- a/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp +++ b/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2025, Andrew Lindesay . + * Copyright 2017-2026, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #include "ServerRepositoryDataUpdateProcess.h" @@ -9,14 +9,12 @@ #include #include -#include #include #include #include -#include "DumpExportRepository.h" #include "DumpExportRepositoryJsonListener.h" -#include "DumpExportRepositorySource.h" +#include "DumpExportRepositoryModel.h" #include "Logger.h" #include "PackageInfo.h" #include "ServerSettings.h" @@ -35,26 +33,22 @@ class DepotMatchingRepositoryListener : public DumpExportRepositoryListener { public: - DepotMatchingRepositoryListener(Model* model, - Stoppable* stoppable); + DepotMatchingRepositoryListener(Model* model, Stoppable* stoppable); virtual ~DepotMatchingRepositoryListener(); - virtual bool Handle(DumpExportRepository* item); - void Handle(DumpExportRepository* repository, - DumpExportRepositorySource* - repositorySource); + virtual bool Handle(DumpExportRepositoryRef item); + void Handle(DumpExportRepositoryRef repository, + DumpExportRepositorySourceRef repositorySource); void Handle(const BString& identifier, - DumpExportRepository* repository, - DumpExportRepositorySource* - repositorySource); + DumpExportRepositoryRef repository, + DumpExportRepositorySourceRef repositorySource); virtual void Complete(); private: void _SetupRepositoryData( DepotInfoRef& depot, - DumpExportRepository* repository, - DumpExportRepositorySource* - repositorySource); + DumpExportRepositoryRef repository, + DumpExportRepositorySourceRef repositorySource); private: Model* fModel; @@ -80,15 +74,15 @@ DepotMatchingRepositoryListener::~DepotMatchingRepositoryListener() void DepotMatchingRepositoryListener::_SetupRepositoryData(DepotInfoRef& depot, - DumpExportRepository* repository, DumpExportRepositorySource* repositorySource) + DumpExportRepositoryRef repository, DumpExportRepositorySourceRef repositorySource) { - BString* repositoryCode = repository->Code(); - BString* repositorySourceCode = repositorySource->Code(); + BString repositoryCode = repository->Code(); + BString repositorySourceCode = repositorySource->Code(); DepotInfoBuilder depotBuilder(depot); - depotBuilder.WithWebAppRepositoryCode(*repositoryCode); - depotBuilder.WithWebAppRepositorySourceCode(*repositorySourceCode); + depotBuilder.WithWebAppRepositoryCode(repositoryCode); + depotBuilder.WithWebAppRepositorySourceCode(repositorySourceCode); DepotInfoRef depotInfo = depotBuilder.BuildRef(); fDepots.push_back(depotInfo); @@ -96,19 +90,19 @@ DepotMatchingRepositoryListener::_SetupRepositoryData(DepotInfoRef& depot, if (Logger::IsDebugEnabled()) { HDDEBUG("[DepotMatchingRepositoryListener] associated depot [%s] (%s) " "with server repository source [%s] (%s)", - depot->Name().String(), depot->Identifier().String(), repositorySourceCode->String(), - repositorySource->Identifier()->String()); + depot->Name().String(), depot->Identifier().String(), repositorySourceCode.String(), + repositorySource->Identifier().String()); } else { HDINFO("[DepotMatchingRepositoryListener] associated depot [%s] with " "server repository source [%s]", - depot->Name().String(), repositorySourceCode->String()); + depot->Name().String(), repositorySourceCode.String()); } } void -DepotMatchingRepositoryListener::Handle(const BString& identifier, DumpExportRepository* repository, - DumpExportRepositorySource* repositorySource) +DepotMatchingRepositoryListener::Handle(const BString& identifier, + DumpExportRepositoryRef repository, DumpExportRepositorySourceRef repositorySource) { if (!identifier.IsEmpty()) { DepotInfoRef depotForIdentifier = fModel->DepotForIdentifier(identifier); @@ -120,22 +114,22 @@ DepotMatchingRepositoryListener::Handle(const BString& identifier, DumpExportRep void -DepotMatchingRepositoryListener::Handle(DumpExportRepository* repository, - DumpExportRepositorySource* repositorySource) +DepotMatchingRepositoryListener::Handle(DumpExportRepositoryRef repository, + DumpExportRepositorySourceRef repositorySource) { - if (!repositorySource->IdentifierIsNull()) - Handle(*(repositorySource->Identifier()), repository, repositorySource); + if (repositorySource->IsSetIdentifier()) + Handle(repositorySource->Identifier(), repository, repositorySource); // there may be additional identifiers for the remote repository and // these should also be taken into consideration. for (int32 i = 0; i < repositorySource->CountExtraIdentifiers(); i++) - Handle(*(repositorySource->ExtraIdentifiersItemAt(i)), repository, repositorySource); + Handle(repositorySource->ExtraIdentifiersItemAt(i), repository, repositorySource); } bool -DepotMatchingRepositoryListener::Handle(DumpExportRepository* repository) +DepotMatchingRepositoryListener::Handle(DumpExportRepositoryRef repository) { int32 i; for (i = 0; i < repository->CountRepositorySources(); i++) diff --git a/src/tests/apps/haikudepot/DumpExportRepositoryJsonListenerTest.cpp b/src/tests/apps/haikudepot/DumpExportRepositoryJsonListenerTest.cpp index f8c6fbac74..828b905812 100644 --- a/src/tests/apps/haikudepot/DumpExportRepositoryJsonListenerTest.cpp +++ b/src/tests/apps/haikudepot/DumpExportRepositoryJsonListenerTest.cpp @@ -1,12 +1,11 @@ /* - * Copyright 2017-2020, Andrew Lindesay . + * Copyright 2017-2026, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ -#include "DumpExportRepositorySource.h" -#include "DumpExportRepository.h" #include "DumpExportRepositoryJsonListener.h" #include "DumpExportRepositoryJsonListenerTest.h" +#include "DumpExportRepositoryModel.h" #include @@ -124,7 +123,7 @@ public: TestBulkContainerItemListener(); virtual ~TestBulkContainerItemListener(); - bool Handle(DumpExportRepository* item); + bool Handle(DumpExportRepositoryRef item); void Complete(); BString ConcatenatedCodes(); @@ -200,21 +199,15 @@ DumpExportRepositoryJsonListenerTest::TestSingle() BPrivate::BJson::Parse(inputData, listener); // ---------------------- - DumpExportRepository *repository = listener->Target(); - ObjectDeleter repositoryDeleter(repository); + DumpExportRepositoryRef repository = listener->Result(); CPPUNIT_ASSERT_EQUAL(B_OK, listener->ErrorStatus()); - CPPUNIT_ASSERT_EQUAL( - BString("haikuports"), *(repository->Code())); - CPPUNIT_ASSERT_EQUAL( - BString("HaikuPorts"), *(repository->Name())); - CPPUNIT_ASSERT_EQUAL( - BString("HaikuPorts is a centralized collection..."), - *(repository->Description())); - CPPUNIT_ASSERT_EQUAL( - BString("https://example.com"), - *(repository->InformationUrl())); + CPPUNIT_ASSERT_EQUAL(BString("haikuports"), repository->Code()); + CPPUNIT_ASSERT_EQUAL(BString("HaikuPorts"), repository->Name()); + CPPUNIT_ASSERT_EQUAL(BString("HaikuPorts is a centralized collection..."), + repository->Description()); + CPPUNIT_ASSERT_EQUAL(BString("https://example.com"), repository->InformationUrl()); CPPUNIT_ASSERT_EQUAL(2, repository->CountRepositorySources()); DumpExportRepositorySource *source0 = @@ -222,12 +215,12 @@ DumpExportRepositoryJsonListenerTest::TestSingle() DumpExportRepositorySource *source1 = repository->RepositorySourcesItemAt(1); - CPPUNIT_ASSERT_EQUAL(BString("haikuports_x86_64"), *(source0->Code())); - CPPUNIT_ASSERT_EQUAL(BString("haiku:hpkr:haikuports_x86_64"), *(source0->Identifier())); - CPPUNIT_ASSERT_EQUAL(BString("zing"), *(source0->ExtraIdentifiersItemAt(0))); + CPPUNIT_ASSERT_EQUAL(BString("haikuports_x86_64"), source0->Code()); + CPPUNIT_ASSERT_EQUAL(BString("haiku:hpkr:haikuports_x86_64"), source0->Identifier()); + CPPUNIT_ASSERT_EQUAL(BString("zing"), source0->ExtraIdentifiersItemAt(0)); - CPPUNIT_ASSERT_EQUAL(BString("haikuports_x86_gcc2"), *(source1->Code())); - CPPUNIT_ASSERT_EQUAL(BString("haiku:hpkr:haikuports_x86_gcc2"), *(source1->Identifier())); + CPPUNIT_ASSERT_EQUAL(BString("haikuports_x86_gcc2"), source1->Code()); + CPPUNIT_ASSERT_EQUAL(BString("haiku:hpkr:haikuports_x86_gcc2"), source1->Identifier()); } @@ -267,21 +260,20 @@ TestBulkContainerItemListener::~TestBulkContainerItemListener() */ bool -TestBulkContainerItemListener::Handle(DumpExportRepository* item) +TestBulkContainerItemListener::Handle(DumpExportRepositoryRef item) { int32 i; if (!fConcatenatedCodes.IsEmpty()) fConcatenatedCodes.Append(" "); - fConcatenatedCodes.Append(item->Code()->String()); + fConcatenatedCodes.Append(item->Code().String()); for (i = 0; i < item->CountRepositorySources(); i++) { if (!fConcatenatedSourcesUrl.IsEmpty()) fConcatenatedSourcesUrl.Append(" "); - fConcatenatedSourcesUrl.Append( - item->RepositorySourcesItemAt(i)->Identifier()->String()); + fConcatenatedSourcesUrl.Append(item->RepositorySourcesItemAt(i)->Identifier().String()); } return true; diff --git a/src/tests/apps/haikudepot/DumpExportRepositoryTest.cpp b/src/tests/apps/haikudepot/DumpExportRepositoryTest.cpp new file mode 100644 index 0000000000..00414266af --- /dev/null +++ b/src/tests/apps/haikudepot/DumpExportRepositoryTest.cpp @@ -0,0 +1,390 @@ +/* + * Copyright 2026, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ + +#include "DumpExportRepositoryTest.h" + +#include +#include + +#include "DumpExportRepositoryModel.h" + + +DumpExportRepositoryTest::DumpExportRepositoryTest() +{ +} + + +DumpExportRepositoryTest::~DumpExportRepositoryTest() +{ +} + + +void +DumpExportRepositoryTest::TestEqualityTrue() +{ + DumpExportRepositoryRef repositoryRef1 = _CreateAny(); + DumpExportRepositoryRef repositoryRef2 = _CreateAny(); + + // ---------------------- + bool result = (*(repositoryRef1.Get()) == *(repositoryRef2.Get())); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("==", true, result); +} + + +void +DumpExportRepositoryTest::TestEqualityFalse() +{ + DumpExportRepositoryRef repositoryRef1 = _CreateAny(); + DumpExportRepositoryRef repositoryRef2 + = DumpExportRepositoryBuilder(repositoryRef1).WithCode("craters").BuildRef(); + + // ---------------------- + bool result = (*(repositoryRef1.Get()) != *(repositoryRef2.Get())); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!=", true, result); +} + + +/*! This test case checks what happens when data changes down the hierarchy of + objects. It should still see this as part of the equality. +*/ +void +DumpExportRepositoryTest::TestEqualityTransientFalse() +{ + DumpExportRepositoryRef repositoryRef1 = _CreateAny(); + DumpExportRepositorySourceRef repositorySourceRef1 = repositoryRef1->RepositorySourcesItemAt(0); + + DumpExportRepositoryRef repositoryRef2 + = DumpExportRepositoryBuilder(repositoryRef1) + .WithoutRepositorySources() + .AddToRepositorySources(DumpExportRepositorySourceBuilder(repositorySourceRef1) + .WithCode("pigeon") + .BuildRef()) + .BuildRef(); + + // ---------------------- + bool result = (*(repositoryRef1.Get()) == *(repositoryRef2.Get())); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("R1 CountRepositorySources", 1, + repositoryRef1->CountRepositorySources()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("R2 CountRepositorySources", 1, + repositoryRef2->CountRepositorySources()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("==", false, result); +} + + +/*! Check to make sure that the Clear method works. */ +void +DumpExportRepositoryTest::TestBuilderClear() +{ + DumpExportRepositoryBuilder builder + = DumpExportRepositoryBuilder().WithCode("oak").WithName("Oak Tree"); + + DumpExportRepositoryRef repositoryBeforeRef = builder.BuildRef(); + + // ---------------------- + builder.Clear(); + // ---------------------- + + DumpExportRepositoryRef repositoryAfterRef = builder.WithCode("birch").BuildRef(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("CodeBefore", BString("oak"), repositoryBeforeRef->Code()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("NameBefore", BString("Oak Tree"), repositoryBeforeRef->Name()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CodeAfter", BString("birch"), repositoryAfterRef->Code()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("IsSetNameAfter", false, repositoryAfterRef->IsSetName()); +} + + +/*! Checks use of the builder without any prior object. */ +void +DumpExportRepositoryTest::TestBuilderFromCold() +{ + + // ---------------------- + DumpExportRepositoryRef repositoryRef = _CreateAny(); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Code", BString("flamingo"), repositoryRef->Code()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("IsSetCode", true, repositoryRef->IsSetCode()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Description", BString("Flamingo is a bird"), + repositoryRef->Description()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Name", BString("Flamingo"), repositoryRef->Name()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("InformationUrl", BString("http://example.com"), + repositoryRef->InformationUrl()); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("SourcesCount", 1, repositoryRef->CountRepositorySources()); + DumpExportRepositorySourceRef repositorySourceRef = repositoryRef->RepositorySourcesItemAt(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Sources0Code", BString("flamingo_x86_64"), + repositorySourceRef->Code()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Sources0Identifier", BString("repo:flamingo"), + repositorySourceRef->Identifier()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Sources0IsSetArchitectureCode", false, + repositorySourceRef->IsSetArchitectureCode()); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("CountRepositorySourceMirrors", 1, + repositorySourceRef->CountRepositorySourceMirrors()); + DumpExportRepositorySourceMirrorRef repositorySourceMirrorRef + = repositorySourceRef->RepositorySourceMirrorsItemAt(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CountryCode", BString("de"), + repositorySourceMirrorRef->CountryCode()); +} + + +void +DumpExportRepositoryTest::TestBuilderFromExisting() +{ + DumpExportRepositoryRef originRepositoryRef = _CreateAny(); + DumpExportRepositoryBuilder builder + = DumpExportRepositoryBuilder(originRepositoryRef).WithCode("flamingo2"); + + // ---------------------- + DumpExportRepositoryRef repositoryRef = builder.BuildRef(); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Code", BString("flamingo2"), repositoryRef->Code()); + // check that the changed field has come through + CPPUNIT_ASSERT_EQUAL_MESSAGE("Name", BString("Flamingo"), repositoryRef->Name()); + // check that the unchanged field has come through + + // check that unmodified subordinate objects are coming through + CPPUNIT_ASSERT_EQUAL_MESSAGE("SourcesCount", 1, repositoryRef->CountRepositorySources()); + DumpExportRepositorySourceRef repositorySourceRef = repositoryRef->RepositorySourcesItemAt(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Sources0Code", BString("flamingo_x86_64"), + repositorySourceRef->Code()); +} + + +void +DumpExportRepositoryTest::TestBuilderFromExistingNoChange() +{ + DumpExportRepositoryRef originRepositoryRef = _CreateAny(); + DumpExportRepositoryBuilder builder = DumpExportRepositoryBuilder(originRepositoryRef); + + // ---------------------- + DumpExportRepositoryRef repositoryRef = builder.BuildRef(); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Code", BString("flamingo"), repositoryRef->Code()); +} + + +void +DumpExportRepositoryTest::TestFromMessage() +{ + BMessage message = _CreateAnyMessage(); + + // ---------------------- + DumpExportRepositoryRef repository(new DumpExportRepository(&message), true); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("R Code", BString("myrepo"), repository->Code()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("IsSetCode", true, repository->IsSetCode()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("IsSetInformationUrl", false, repository->IsSetInformationUrl()); + // ^ specifically check that one of the missing fields is showing as missing. + CPPUNIT_ASSERT_EQUAL_MESSAGE("CountRepositorySources", 1, repository->CountRepositorySources()); + + DumpExportRepositorySourceRef source0 = repository->RepositorySourcesItemAt(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("RS Code", BString("myrepo_x86_64"), source0->Code()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("IsSetCode", true, source0->IsSetCode()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CountExtraIdentifiers", 2, source0->CountExtraIdentifiers()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("ExtraIdentifier_0", BString("extra_ident:myrepo_x86_64_0"), + source0->ExtraIdentifiersItemAt(0)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("ExtraIdentifier_1", BString("extra_ident:myrepo_x86_64_1"), + source0->ExtraIdentifiersItemAt(1)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CountRepositorySourceMirrors", 1, + source0->CountRepositorySourceMirrors()); + + DumpExportRepositorySourceMirrorRef mirror0 = source0->RepositorySourceMirrorsItemAt(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CountryCode", BString("nz"), mirror0->CountryCode()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("IsPrimary", true, mirror0->IsPrimary()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("IsSetIsPrimary", true, mirror0->IsSetIsPrimary()); +} + + +void +DumpExportRepositoryTest::TestToMessage() +{ + DumpExportRepositoryRef repositoryRef = _CreateAny(); + BMessage repositoryMessage; + + // ---------------------- + status_t result = repositoryRef->Archive(&repositoryMessage, true); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Result", B_OK, result); + + // quick checks as this could get quite laborious. + status_t status; + BString anyString; + bool anyBool; + + status = repositoryMessage.FindString("Code", &anyString); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Code Status", B_OK, status); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Code", BString("flamingo"), anyString); + + status = repositoryMessage.FindBool("RepositorySources_isSet", &anyBool); + CPPUNIT_ASSERT_EQUAL_MESSAGE("RepositorySources_isSet Status", B_OK, status); + CPPUNIT_ASSERT_EQUAL_MESSAGE("RepositorySources_isSet", true, anyBool); + + BMessage repositorySource0Message; + status = repositoryMessage.FindMessage("RepositorySources_0", &repositorySource0Message); + CPPUNIT_ASSERT_EQUAL_MESSAGE("RepositorySources_0 Status", B_OK, status); + + status = repositorySource0Message.FindString("Code", &anyString); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Code Status", B_OK, status); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Code", BString("flamingo_x86_64"), anyString); + + status = repositorySource0Message.FindBool("ExtraIdentifiers_isSet", &anyBool); + CPPUNIT_ASSERT_EQUAL_MESSAGE("ExtraIdentifiers_isSet Status", B_OK, status); + CPPUNIT_ASSERT_EQUAL_MESSAGE("ExtraIdentifiers_isSet", true, anyBool); + + status = repositorySource0Message.FindString("ExtraIdentifiers_0", &anyString); + CPPUNIT_ASSERT_EQUAL_MESSAGE("ExtraIdentifiers_0 Status", B_OK, status); + CPPUNIT_ASSERT_EQUAL_MESSAGE("ExtraIdentifiers_0", BString("repo:flamingo2"), anyString); + + status = repositorySource0Message.FindBool("RepositorySourceMirrors_isSet", &anyBool); + CPPUNIT_ASSERT_EQUAL_MESSAGE("RepositorySourceMirrors_isSet Status", B_OK, status); + CPPUNIT_ASSERT_EQUAL_MESSAGE("RepositorySourceMirrors_isSet", true, anyBool); + + BMessage repositorySourceMirror0Message; + status = repositorySource0Message.FindMessage("RepositorySourceMirrors_0", + &repositorySourceMirror0Message); + CPPUNIT_ASSERT_EQUAL_MESSAGE("RepositorySourceMirrors_0 Status", B_OK, status); + + status = repositorySourceMirror0Message.FindString("CountryCode", &anyString); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CountryCode Status", B_OK, status); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CountryCode", BString("de"), anyString); +} + + +/*static*/ void +DumpExportRepositoryTest::AddTests(BTestSuite& parent) +{ + CppUnit::TestSuite& suite = *new CppUnit::TestSuite("DumpExportRepositoryTest"); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestEqualityTrue", &DumpExportRepositoryTest::TestEqualityTrue)); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestEqualityFalse", + &DumpExportRepositoryTest::TestEqualityFalse)); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestEqualityTransientFalse", + &DumpExportRepositoryTest::TestEqualityTransientFalse)); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestBuilderFromCold", + &DumpExportRepositoryTest::TestBuilderFromCold)); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestBuilderFromExisting", + &DumpExportRepositoryTest::TestBuilderFromExisting)); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestBuilderFromExistingNoChange", + &DumpExportRepositoryTest::TestBuilderFromExistingNoChange)); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestBuilderClear", &DumpExportRepositoryTest::TestBuilderClear)); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestFromMessage", &DumpExportRepositoryTest::TestFromMessage)); + + suite.addTest(new CppUnit::TestCaller( + "DumpExportRepositoryTest::TestToMessage", &DumpExportRepositoryTest::TestToMessage)); + + parent.addTest("DumpExportRepositoryTest", &suite); +} + + +/*! This is a `BMessage` that is an archive of an `DumpExportRepository` object. */ +BMessage +DumpExportRepositoryTest::_CreateAnyMessage() +{ + BMessage repositorySourceMirrorMessage; + + if (repositorySourceMirrorMessage.AddString("BaseUrl", "http://www.example.com/mirror_baseurl") + != B_OK) { + CPPUNIT_FAIL("unable to add the key [BaseUrl]"); + } + if (repositorySourceMirrorMessage.AddString("CountryCode", "nz") != B_OK) + CPPUNIT_FAIL("unable to add the key [CountryCode]"); + if (repositorySourceMirrorMessage.AddString("Description", "New Zealand mirror") != B_OK) + CPPUNIT_FAIL("unable to add the key [Description]"); + if (repositorySourceMirrorMessage.AddBool("IsPrimary", true) != B_OK) + CPPUNIT_FAIL("unable to add the key [IsPrimary]"); + + BMessage repositorySourceMessage; + + if (repositorySourceMessage.AddString("ArchitectureCode", "x86_64") != B_OK) + CPPUNIT_FAIL("unable to add the key [ArchitectureCode]"); + if (repositorySourceMessage.AddString("Code", "myrepo_x86_64") != B_OK) + CPPUNIT_FAIL("unable to add the key [Code]"); + if (repositorySourceMessage.AddBool("ExtraIdentifiers_isSet", true) != B_OK) + CPPUNIT_FAIL("unable to add the key [ExtraIdentifiers_isSet]"); + if (repositorySourceMessage.AddString("ExtraIdentifiers_0", "extra_ident:myrepo_x86_64_0") + != B_OK) { + CPPUNIT_FAIL("unable to add the key [ExtraIdentifiers_0]"); + } + if (repositorySourceMessage.AddString("ExtraIdentifiers_1", "extra_ident:myrepo_x86_64_1") + != B_OK) { + CPPUNIT_FAIL("unable to add the key [ExtraIdentifiers_1]"); + } + if (repositorySourceMessage.AddString("Identifier", "ident:myrepo_x86_64") != B_OK) + CPPUNIT_FAIL("unable to add the key [Identifier]"); + if (repositorySourceMessage.AddString("RepoInfoUrl", "http://www.example.com/myrepo_x86_64") + != B_OK) { + CPPUNIT_FAIL("unable to add the key [RepoInfoUrl]"); + } + if (repositorySourceMessage.AddBool("RepositorySourceMirrors_isSet", true) != B_OK) + CPPUNIT_FAIL("unable to add the key [RepositorySourceMirrors_isSet]"); + if (repositorySourceMessage.AddMessage("RepositorySourceMirrors_0", + &repositorySourceMirrorMessage) + != B_OK) { + CPPUNIT_FAIL("unable to add the key [RepositorySourceMirrors_0]"); + } + + BMessage repositoryMessage; + + if (repositoryMessage.AddString("Code", "myrepo") != B_OK) + CPPUNIT_FAIL("unable to add the key [Code]"); + if (repositoryMessage.AddString("Description", "This is my repo") != B_OK) + CPPUNIT_FAIL("unable to add the key [Description]"); + // note; missing the informational url intentionally + if (repositoryMessage.AddString("Name", "My Repo") != B_OK) + CPPUNIT_FAIL("unable to add the key [Name]"); + if (repositoryMessage.AddBool("RepositorySources_isSet", true) != B_OK) + CPPUNIT_FAIL("unable to add the key [RepositorySources_isSet]"); + if (repositoryMessage.AddMessage("RepositorySources_0", &repositorySourceMessage) != B_OK) + CPPUNIT_FAIL("unable to add the key [RepositorySources_0]"); + + return repositoryMessage; +} + + +DumpExportRepositoryRef +DumpExportRepositoryTest::_CreateAny() +{ + return DumpExportRepositoryBuilder() + .WithCode("flamingo") + .WithDescription("Flamingo is a bird") + .WithInformationUrl("http://example.com") + .WithName("Flamingo") + .AddToRepositorySources(DumpExportRepositorySourceBuilder() + .WithCode("flamingo_x86_64") + .WithIdentifier("repo:flamingo") + .AddToExtraIdentifiers("repo:flamingo2") + .AddToRepositorySourceMirrors(DumpExportRepositorySourceMirrorBuilder() + .WithCountryCode("de") + .WithBaseUrl("http://www.example.com/base_url") + .BuildRef()) + .BuildRef()) + .BuildRef(); +} diff --git a/src/tests/apps/haikudepot/DumpExportRepositoryTest.h b/src/tests/apps/haikudepot/DumpExportRepositoryTest.h new file mode 100644 index 0000000000..46daacb767 --- /dev/null +++ b/src/tests/apps/haikudepot/DumpExportRepositoryTest.h @@ -0,0 +1,43 @@ +/* + * Copyright 2026, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#ifndef DUMP_EXPORT_REPOSITORY_TEST_H +#define DUMP_EXPORT_REPOSITORY_TEST_H + +#include + +#include +#include + +#include "DumpExportRepositoryModel.h" + + +/*! Tests the model in insolation from the listener. */ +class DumpExportRepositoryTest : public CppUnit::TestCase { +public: + DumpExportRepositoryTest(); + virtual ~DumpExportRepositoryTest(); + + void TestEqualityTrue(); + void TestEqualityFalse(); + void TestEqualityTransientFalse(); + + void TestBuilderFromCold(); + void TestBuilderFromExisting(); + void TestBuilderFromExistingNoChange(); + void TestBuilderClear(); + + void TestFromMessage(); + void TestToMessage(); + + static void AddTests(BTestSuite& suite); + +private: + DumpExportRepositoryRef + _CreateAny(); + BMessage _CreateAnyMessage(); +}; + + +#endif // DUMP_EXPORT_REPOSITORY_TEST_H diff --git a/src/tests/apps/haikudepot/HaikuDepotTestAddon.cpp b/src/tests/apps/haikudepot/HaikuDepotTestAddon.cpp index fc5ff56d43..3f8d01cf86 100644 --- a/src/tests/apps/haikudepot/HaikuDepotTestAddon.cpp +++ b/src/tests/apps/haikudepot/HaikuDepotTestAddon.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024, Andrew Lindesay, apl@lindesay.co.nz + * Copyright 2017-2026, Andrew Lindesay, apl@lindesay.co.nz * Distributed under the terms of the MIT License. */ @@ -9,6 +9,7 @@ #include "DataIOUtilsTest.h" #include "DumpExportRepositoryJsonListenerTest.h" +#include "DumpExportRepositoryTest.h" #include "JwtTokenHelperTest.h" #include "LocaleUtilsTest.h" #include "StandardMetaDataJsonEventListenerTest.h" @@ -27,7 +28,7 @@ getTestSuite() StandardMetaDataJsonEventListenerTest::AddTests(*suite); DataIOUtilsTest::AddTests(*suite); DumpExportRepositoryJsonListenerTest::AddTests(*suite); - DumpExportRepositoryJsonListenerTest::AddTests(*suite); + DumpExportRepositoryTest::AddTests(*suite); LocaleUtilsTest::AddTests(*suite); JwtTokenHelperTest::AddTests(*suite); ValidationFailureTest::AddTests(*suite); diff --git a/src/tests/apps/haikudepot/Jamfile b/src/tests/apps/haikudepot/Jamfile index 7928dbde93..ff5ec3fa2d 100644 --- a/src/tests/apps/haikudepot/Jamfile +++ b/src/tests/apps/haikudepot/Jamfile @@ -2,7 +2,7 @@ SubDir HAIKU_TOP src tests apps haikudepot ; AddSubDirSupportedPlatforms libbe_test ; -include [ FDirName $(HAIKU_TOP) src apps haikudepot build jam HdsSchemaGenRules ] ; +include [ FDirName $(HAIKU_TOP) src apps haikudepot build jam JsonSchemaGenRules ] ; SubDirHdrs [ FDirName $(HAIKU_TOP) src apps haikudepot ] ; SubDirHdrs [ FDirName $(HAIKU_TOP) src apps haikudepot model ] ; @@ -27,52 +27,50 @@ local sourceDir ; for sourceDir in $(sourceDirs) { SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src apps haikudepot $(sourceDir) ] ; } -local dumpExportRepositoryBulkListenerTargetDirectory = - [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) - dumpexportrepositorybulklistener ] ; -local dumpExportRepositoryModelTargetDirectory = - [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) - dumpexportrepositorymodel ] ; -SEARCH_SOURCE += $(dumpExportRepositoryBulkListenerTargetDirectory) ; -SEARCH_SOURCE += $(dumpExportRepositoryModelTargetDirectory) ; - -local generatedRepositoryModelSourceFiles = - DumpExportRepository.cpp - DumpExportRepositorySource.cpp - DumpExportRepositorySourceMirror.cpp +local genServerDumpExportRepositoryListenerTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportrepositorylistener ] ; +local genServerDumpExportRepositoryModelTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportrepositorymodel ] ; + +SEARCH_SOURCE += $(genServerDumpExportRepositoryListenerTargetDirectory) ; +SEARCH_SOURCE += $(genServerDumpExportRepositoryModelTargetDirectory) ; + +local genServerDumpExportRepositorySchema = + [ FDirName $(HAIKU_TOP) src apps haikudepot server schema dumpexportrepository.json ] ; + +local genServerRepositoryModelSourceFiles = + DumpExportRepositoryModel.cpp ; -local generatedRepositoryParserSourceFiles = +local genServerRepositoryParserSourceFiles = DumpExportRepositoryJsonListener.cpp ; -LOCATE on dumpexportrepository.json = [ FDirName $(HAIKU_TOP) src apps haikudepot server schema ] ; -LOCATE on jsonschema2cppmodel.py = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts ] ; -LOCATE on jsonschema2cppparser.py = [ FDirName $(HAIKU_TOP) src apps haikudepot build scripts ] ; +local genDummyFile = "dummy.dat" ; -local dummyFile = "dummy.dat" ; +local genServerDumpExportRepositoryListenerTargetFile = + [ FDirName $(genServerDumpExportRepositoryListenerTargetDirectory) $(genDummyFile) ] ; +local genServerDumpExportRepositoryModelTargetFile = + [ FDirName $(genServerDumpExportRepositoryModelTargetDirectory) $(genDummyFile) ] ; -local dumpExportRepositoryBulkListenerTargetFile = - [ FDirName $(dumpExportRepositoryBulkListenerTargetDirectory) $(dummyFile) ] ; -local dumpExportRepositoryModelTargetFile = - [ FDirName $(dumpExportRepositoryModelTargetDirectory) $(dummyFile) ] ; +JsonSchemaGenModel $(genServerDumpExportRepositoryModelTargetFile) + : $(genServerDumpExportRepositorySchema) ; -HdsSchemaGenModel $(dumpExportRepositoryModelTargetFile) - : dumpexportrepository.json : jsonschema2cppmodel.py ; +JsonSchemaGenParser $(genServerDumpExportRepositoryListenerTargetFile) + : $(genServerDumpExportRepositorySchema) ; -HdsSchemaGenBulkParser $(dumpExportRepositoryBulkListenerTargetFile) - : dumpexportrepository.json : jsonschema2cppparser.py ; - -HdsSchemaGenAppSrcDependsOnGeneration - [ FGristFiles $(generatedRepositoryParserSourceFiles) ] - [ FGristFiles $(generatedRepositoryParserSourceFiles:S=.h) ] - : $(dumpExportRepositoryBulkListenerTargetDirectory) - : $(dumpExportRepositoryBulkListenerTargetFile) ; +JsonSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(genServerRepositoryParserSourceFiles) ] + [ FGristFiles $(genServerRepositoryParserSourceFiles:S=.h) ] + : $(genServerDumpExportRepositoryListenerTargetDirectory) + : $(genServerDumpExportRepositoryListenerTargetFile) +; -HdsSchemaGenAppSrcDependsOnGeneration - [ FGristFiles $(generatedRepositoryModelSourceFiles) ] - [ FGristFiles $(generatedRepositoryModelSourceFiles:S=.h) ] - : $(dumpExportRepositoryModelTargetDirectory) - : $(dumpExportRepositoryModelTargetFile) ; +JsonSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(genServerRepositoryModelSourceFiles) ] + [ FGristFiles $(genServerRepositoryModelSourceFiles:S=.h) ] + : $(genServerDumpExportRepositoryModelTargetDirectory) + : $(genServerDumpExportRepositoryModelTargetFile) +; UnitTestLib haikudepottest.so : HaikuDepotTestAddon.cpp @@ -80,9 +78,10 @@ UnitTestLib haikudepottest.so : DataIOUtils.cpp DataIOUtilsTest.cpp - $(generatedRepositoryModelSourceFiles) - $(generatedRepositoryParserSourceFiles) + $(genServerRepositoryModelSourceFiles) + $(genServerRepositoryParserSourceFiles) DumpExportRepositoryJsonListenerTest.cpp + DumpExportRepositoryTest.cpp JwtTokenHelper.cpp JwtTokenHelperTest.cpp diff --git a/src/tests/apps/haikudepot/LocaleUtilsTest.cpp b/src/tests/apps/haikudepot/LocaleUtilsTest.cpp index c9eb5d1045..f3d5d9a9ab 100644 --- a/src/tests/apps/haikudepot/LocaleUtilsTest.cpp +++ b/src/tests/apps/haikudepot/LocaleUtilsTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024, Andrew Lindesay . + * Copyright 2024-2026, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -35,7 +35,7 @@ LocaleUtilsTest::TestLanguageIsBeforeFalseAfter() LanguageRef languageDeCh = LanguageRef(new Language("de_CH", "German (Swiss)", true), true); // ---------------------- - bool actual = languageZh < languageDeCh; + bool actual = (*(languageZh.Get()) < *(languageDeCh.Get())); // ---------------------- CPPUNIT_ASSERT_EQUAL(false, actual); @@ -48,7 +48,7 @@ LocaleUtilsTest::TestLanguageIsBeforeFalseEqual() LanguageRef languageDeCh = LanguageRef(new Language("de_CH", "German (Swiss)", true), true); // ---------------------- - bool actual = languageDeCh < languageDeCh; + bool actual = (*(languageDeCh.Get()) < *(languageDeCh.Get())); // ---------------------- CPPUNIT_ASSERT_EQUAL(false, actual); @@ -62,7 +62,7 @@ LocaleUtilsTest::TestLanguageIsBeforeTrueBefore() LanguageRef languageDeCh = LanguageRef(new Language("de_CH", "German (Swiss)", true), true); // ---------------------- - bool actual = languageDeCh < languageZh; + bool actual = (*(languageDeCh.Get()) < *(languageZh.Get())); // ---------------------- CPPUNIT_ASSERT_EQUAL(true, actual);