HaikuDepot: code-generation improvements

The HaikuDepot server's schemas are surfaced into HaikuDepot and then
code-generated so that they can easily stay at parity with the server
and any issues will be caught at build-time.

Changes included;

- improves the Jam build files' readability
- changes the generated models to be immutable
- changes the generated models to use `BReferenceable`
- changes the creation of the models to use a builder pattern
- changes string fields to use BString instead of BString*
- outputs the model to a single file with ordered classes
- makes corresponding changes to the listener for the model changes
- changes the listener to use references instead of object pointers

Purpose;

- improve / tidy the as-built code generation system
- prepare the system for potential use with REST APIs later
- prepare the system for potential use for internal models

Notable file modifications;

- the file `HdsSchemaGenRules` has been renamed to
  `JsonSchemaGenRules` and edited
- the file `hdsjsonschemacommon.py` has been renamed to
  `jsonschemacommon.py` and edited
- the templates from `jsonschema2cppmodel.py` have been
  externalized into `modelheader.mustache` and
  `modelimplementation.mustache`
- the templates from `jsonschema2cppparser.py` have been
  externalized into `parserheader.mustache` and
  `parserimplementation.mustache`

Change-Id: I809efd2e40d9227939ab83f18f1deda27873f03f
Reviewed-on: https://review.haiku-os.org/c/haiku/+/10612
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: Jérôme Duval <[email protected]>
This commit is contained in:
Andrew Lindesay
2026-04-07 12:22:16 +00:00
parent 112776eead
commit 20121b15ad
23 changed files with 2695 additions and 1840 deletions
+119 -133
View File
@@ -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) ]
;
@@ -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) ;
}
@@ -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)
}
@@ -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)
@@ -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 <ObjectList.h>
#include <String.h>
{{#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__":
File diff suppressed because it is too large Load Diff
@@ -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()
@@ -0,0 +1,101 @@
/*
* Generated Model Object for {{outputfilename}}
*/
#ifndef GEN_JSON_SCHEMA_MODEL__{{outputfilestemupper}}_H
#define GEN_JSON_SCHEMA_MODEL__{{outputfilestemupper}}_H
#include <vector>
#include <Archivable.h>
#include <Referenceable.h>
#include <String.h>{{#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
@@ -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}}
@@ -0,0 +1,89 @@
/*
* Generated Listener Object for {{outputfilename}}
*/
#ifndef GEN_JSON_SCHEMA_PARSER__{{outputfilestemupper}}_H
#define GEN_JSON_SCHEMA_PARSER__{{outputfilestemupper}}_H
#include <JsonEventListener.h>
#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
@@ -0,0 +1,916 @@
/*
* Generated Listener Object for {{outputfilename}}
*/
#include "{{toplevel.cppname}}JsonListener.h"
#include <ObjectList.h>
#include <stdio.h>
// #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<BString>& 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<BString> fAccumulator;
bool (*fCallback)(void* context, const std::vector<BString>& 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<BString>& 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<std::vector<{{cppname}}Ref>*>(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;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2020, Andrew Lindesay <[email protected]>.
* Copyright 2018-2026, Andrew Lindesay <[email protected]>.
* 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());
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2025, Andrew Lindesay <[email protected]>.
* Copyright 2017-2026, Andrew Lindesay <[email protected]>.
* 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:
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2025, Andrew Lindesay <[email protected]>.
* Copyright 2017-2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -17,11 +17,8 @@
#include <StopWatch.h>
#include <Url.h>
#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<off_t>(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<uint32>(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<int32>(screenshot->Width()),
new ScreenshotInfo(screenshot->Code(), static_cast<int32>(screenshot->Width()),
static_cast<int32>(screenshot->Height()), static_cast<int32>(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());
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2025, Andrew Lindesay <[email protected]>.
* Copyright 2019-2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -11,17 +11,12 @@
#include <time.h>
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <Catalog.h>
#include <FileIO.h>
#include <Url.h>
#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<SingleDumpExportReferenceJsonListener> 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));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2020, Andrew Lindesay <[email protected]>.
* Copyright 2019-2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef SERVER_REFERENCE_DATA_UPDATE_PROCESS_H
@@ -13,6 +13,7 @@
#include <String.h>
#include <Url.h>
#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;
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2025, Andrew Lindesay <[email protected]>.
* Copyright 2017-2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "ServerRepositoryDataUpdateProcess.h"
@@ -9,14 +9,12 @@
#include <time.h>
#include <AutoDeleter.h>
#include <Autolock.h>
#include <Catalog.h>
#include <FileIO.h>
#include <Url.h>
#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++)
@@ -1,12 +1,11 @@
/*
* Copyright 2017-2020, Andrew Lindesay <[email protected]>.
* Copyright 2017-2026, Andrew Lindesay <[email protected]>.
* 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 <stdio.h>
@@ -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<DumpExportRepository> 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;
@@ -0,0 +1,390 @@
/*
* Copyright 2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "DumpExportRepositoryTest.h"
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#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>(
"DumpExportRepositoryTest::TestEqualityTrue", &DumpExportRepositoryTest::TestEqualityTrue));
suite.addTest(new CppUnit::TestCaller<DumpExportRepositoryTest>(
"DumpExportRepositoryTest::TestEqualityFalse",
&DumpExportRepositoryTest::TestEqualityFalse));
suite.addTest(new CppUnit::TestCaller<DumpExportRepositoryTest>(
"DumpExportRepositoryTest::TestEqualityTransientFalse",
&DumpExportRepositoryTest::TestEqualityTransientFalse));
suite.addTest(new CppUnit::TestCaller<DumpExportRepositoryTest>(
"DumpExportRepositoryTest::TestBuilderFromCold",
&DumpExportRepositoryTest::TestBuilderFromCold));
suite.addTest(new CppUnit::TestCaller<DumpExportRepositoryTest>(
"DumpExportRepositoryTest::TestBuilderFromExisting",
&DumpExportRepositoryTest::TestBuilderFromExisting));
suite.addTest(new CppUnit::TestCaller<DumpExportRepositoryTest>(
"DumpExportRepositoryTest::TestBuilderFromExistingNoChange",
&DumpExportRepositoryTest::TestBuilderFromExistingNoChange));
suite.addTest(new CppUnit::TestCaller<DumpExportRepositoryTest>(
"DumpExportRepositoryTest::TestBuilderClear", &DumpExportRepositoryTest::TestBuilderClear));
suite.addTest(new CppUnit::TestCaller<DumpExportRepositoryTest>(
"DumpExportRepositoryTest::TestFromMessage", &DumpExportRepositoryTest::TestFromMessage));
suite.addTest(new CppUnit::TestCaller<DumpExportRepositoryTest>(
"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();
}
@@ -0,0 +1,43 @@
/*
* Copyright 2026, Andrew Lindesay <[email protected]>
* Distributed under the terms of the MIT License.
*/
#ifndef DUMP_EXPORT_REPOSITORY_TEST_H
#define DUMP_EXPORT_REPOSITORY_TEST_H
#include <Message.h>
#include <TestCase.h>
#include <TestSuite.h>
#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
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2024, Andrew Lindesay, [email protected]
* Copyright 2017-2026, Andrew Lindesay, [email protected]
* 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);
+39 -40
View File
@@ -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
@@ -1,5 +1,5 @@
/*
* Copyright 2024, Andrew Lindesay <[email protected]>.
* Copyright 2024-2026, Andrew Lindesay <[email protected]>.
* 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);