1#!/usr/bin/python 2 3# ===================================== 4# Copyright 2017-2020, Andrew Lindesay 5# Distributed under the terms of the MIT License. 6# ===================================== 7 8# This simple tool will read a JSON schema and will then generate 9# some model objects that can be used to hold the data-structure 10# in the C++ environment. 11 12import json 13import argparse 14import os 15import hdsjsonschemacommon as jscom 16import string 17 18 19def hasanylistproperties(schema): 20 for propname, propmetadata in schema['properties'].items(): 21 if propmetadata['type'] == jscom.JSON_TYPE_ARRAY: 22 return True 23 return False 24 25 26def writelistaccessors(outputfile, cppclassname, cppname, cppmembername, cppcontainertype): 27 28 dict = { 29 'cppclassname' : cppclassname, 30 'cppname': cppname, 31 'cppmembername': cppmembername, 32 'cppcontainertype': cppcontainertype 33 } 34 35 outputfile.write( 36 string.Template(""" 37void 38${cppclassname}::AddTo${cppname}(${cppcontainertype}* value) 39{ 40 if (${cppmembername} == NULL) 41 ${cppmembername} = new BObjectList<${cppcontainertype}>(); 42 ${cppmembername}->AddItem(value); 43} 44 45 46void 47${cppclassname}::Set${cppname}(BObjectList<${cppcontainertype}>* value) 48{ 49 ${cppmembername} = value; 50} 51 52 53int32 54${cppclassname}::Count${cppname}() 55{ 56 if (${cppmembername} == NULL) 57 return 0; 58 return ${cppmembername}->CountItems(); 59} 60 61 62${cppcontainertype}* 63${cppclassname}::${cppname}ItemAt(int32 index) 64{ 65 return ${cppmembername}->ItemAt(index); 66} 67 68 69bool 70${cppclassname}::${cppname}IsNull() 71{ 72 return ${cppmembername} == NULL; 73} 74""").substitute(dict)) 75 76 77def writelistaccessorsheader(outputfile, cppname, cppcontainertype): 78 dict = { 79 'cppname': cppname, 80 'cppcontainertype': cppcontainertype 81 } 82 83 outputfile.write( 84 string.Template(""" void AddTo${cppname}(${cppcontainertype}* value); 85 void Set${cppname}(BObjectList<${cppcontainertype}>* value); 86 int32 Count${cppname}(); 87 ${cppcontainertype}* ${cppname}ItemAt(int32 index); 88 bool ${cppname}IsNull(); 89""").substitute(dict)) 90 91 92def writetakeownershipaccessors(outputfile, cppclassname, cppname, cppmembername, cpptype): 93 94 dict = { 95 'cppclassname': cppclassname, 96 'cppname': cppname, 97 'cppmembername': cppmembername, 98 'cpptype': cpptype 99 } 100 101 outputfile.write( 102 string.Template(""" 103${cpptype}* 104${cppclassname}::${cppname}() 105{ 106 return ${cppmembername}; 107} 108 109 110void 111${cppclassname}::Set${cppname}(${cpptype}* value) 112{ 113 ${cppmembername} = value; 114} 115 116 117void 118${cppclassname}::Set${cppname}Null() 119{ 120 if (!${cppname}IsNull()) { 121 delete ${cppmembername}; 122 ${cppmembername} = NULL; 123 } 124} 125 126 127bool 128${cppclassname}::${cppname}IsNull() 129{ 130 return ${cppmembername} == NULL; 131} 132""").substitute(dict)) 133 134 135def writetakeownershipaccessorsheader(outputfile, cppname, cpptype): 136 outputfile.write(' %s* %s();\n' % (cpptype, cppname)) 137 outputfile.write(' void Set%s(%s* value);\n' % (cppname, cpptype)) 138 outputfile.write(' void Set%sNull();\n' % cppname) 139 outputfile.write(' bool %sIsNull();\n' % cppname) 140 141 142def writescalaraccessors(outputfile, cppclassname, cppname, cppmembername, cpptype): 143 144 dict = { 145 'cppclassname': cppclassname, 146 'cppname': cppname, 147 'cppmembername': cppmembername, 148 'cpptype': cpptype 149 } 150 151 outputfile.write( 152 string.Template(""" 153${cpptype} 154${cppclassname}::${cppname}() 155{""").substitute(dict)) 156 157 if cpptype == jscom.CPP_TYPE_BOOLEAN: 158 outputfile.write(string.Template(""" 159 if (${cppname}IsNull()) 160 return false; 161""").substitute(dict)) 162 163 if cpptype == jscom.CPP_TYPE_INTEGER: 164 outputfile.write(string.Template(""" 165 if (${cppname}IsNull()) 166 return 0; 167""").substitute(dict)) 168 169 if cpptype == jscom.CPP_TYPE_NUMBER: 170 outputfile.write(string.Template(""" 171 if (${cppname}IsNull()) 172 return 0.0; 173""").substitute(dict)) 174 175 outputfile.write(string.Template(""" 176 return *${cppmembername}; 177} 178 179 180void 181${cppclassname}::Set${cppname}(${cpptype} value) 182{ 183 if (${cppname}IsNull()) 184 ${cppmembername} = new ${cpptype}[1]; 185 ${cppmembername}[0] = value; 186} 187 188 189void 190${cppclassname}::Set${cppname}Null() 191{ 192 if (!${cppname}IsNull()) { 193 delete ${cppmembername}; 194 ${cppmembername} = NULL; 195 } 196} 197 198 199bool 200${cppclassname}::${cppname}IsNull() 201{ 202 return ${cppmembername} == NULL; 203} 204""").substitute(dict)) 205 206 207def writescalaraccessorsheader(outputfile, cppname, cpptype): 208 outputfile.write( 209 string.Template(""" 210 ${cpptype} ${cppname}(); 211 void Set${cppname}(${cpptype} value); 212 void Set${cppname}Null(); 213 bool ${cppname}IsNull(); 214""").substitute({'cppname': cppname, 'cpptype': cpptype})) 215 216 217def writeaccessors(outputfile, cppclassname, propname, propmetadata): 218 type = propmetadata['type'] 219 220 if type == jscom.JSON_TYPE_ARRAY: 221 writelistaccessors(outputfile, 222 cppclassname, 223 jscom.propnametocppname(propname), 224 jscom.propnametocppmembername(propname), 225 jscom.propmetadatatocpptypename(propmetadata['items'])) 226 elif jscom.propmetadatatypeisscalar(propmetadata): 227 writescalaraccessors(outputfile, 228 cppclassname, 229 jscom.propnametocppname(propname), 230 jscom.propnametocppmembername(propname), 231 jscom.propmetadatatocpptypename(propmetadata)) 232 else: 233 writetakeownershipaccessors(outputfile, 234 cppclassname, 235 jscom.propnametocppname(propname), 236 jscom.propnametocppmembername(propname), 237 jscom.propmetadatatocpptypename(propmetadata)) 238 239 240def writeaccessorsheader(outputfile, propname, propmetadata): 241 type = propmetadata['type'] 242 243 if type == jscom.JSON_TYPE_ARRAY: 244 writelistaccessorsheader(outputfile, 245 jscom.propnametocppname(propname), 246 jscom.propmetadatatocpptypename(propmetadata['items'])) 247 elif jscom.propmetadatatypeisscalar(propmetadata): 248 writescalaraccessorsheader(outputfile, 249 jscom.propnametocppname(propname), 250 jscom.propmetadatatocpptypename(propmetadata)) 251 else: 252 writetakeownershipaccessorsheader(outputfile, 253 jscom.propnametocppname(propname), 254 jscom.propmetadatatocpptypename(propmetadata)) 255 256 257def writedestructorlogicforlist(outputfile, propname, propmetadata): 258 dict = { 259 'cppmembername': jscom.propnametocppmembername(propname), 260 'cpptype': jscom.javatypetocppname(propmetadata['items']['javaType']) 261 } 262 263 outputfile.write( 264 string.Template(""" int32 count = ${cppmembername}->CountItems(); 265 for (i = 0; i < count; i++) 266 delete ${cppmembername}->ItemAt(i); 267""").substitute(dict)) 268 269 270def writedestructor(outputfile, cppname, schema): 271 outputfile.write('\n\n%s::~%s()\n{\n' % (cppname, cppname)) 272 273 if hasanylistproperties(schema): 274 outputfile.write(' int32 i;\n\n') 275 276 for propname, propmetadata in schema['properties'].items(): 277 propmembername = jscom.propnametocppmembername(propname) 278 279 outputfile.write(' if (%s != NULL) {\n' % propmembername) 280 281 if propmetadata['type'] == jscom.JSON_TYPE_ARRAY: 282 writedestructorlogicforlist(outputfile, propname, propmetadata) 283 284 outputfile.write(( 285 ' delete %s;\n' 286 ) % propmembername) 287 288 outputfile.write(' }\n\n') 289 290 outputfile.write('}\n') 291 292 293def writeconstructor(outputfile, cppname, schema): 294 outputfile.write('\n\n%s::%s()\n{\n' % (cppname, cppname)) 295 296 for propname, propmetadata in schema['properties'].items(): 297 outputfile.write(' %s = NULL;\n' % jscom.propnametocppmembername(propname)) 298 299 outputfile.write('}\n') 300 301 302def writeheaderincludes(outputfile, properties): 303 for propname, propmetadata in properties.items(): 304 jsontype = propmetadata['type'] 305 javatype = None 306 307 if jsontype == jscom.JSON_TYPE_OBJECT: 308 javatype = propmetadata['javaType'] 309 310 if jsontype == jscom.JSON_TYPE_ARRAY: 311 javatype = propmetadata['items']['javaType'] 312 313 if javatype is not None: 314 outputfile.write('#include "%s.h"\n' % jscom.javatypetocppname(javatype)) 315 316 317def schematocppmodels(inputfile, schema, outputdirectory): 318 type = schema['type'] 319 if type != jscom.JSON_TYPE_OBJECT: 320 raise Exception('expecting object, but was [' + type + ']') 321 322 javatype = schema['javaType'] 323 324 if not javatype or 0 == len(javatype): 325 raise Exception('missing "javaType" field') 326 327 cppclassname = jscom.javatypetocppname(javatype) 328 cpphfilename = os.path.join(outputdirectory, cppclassname + '.h') 329 cppifilename = os.path.join(outputdirectory, cppclassname + '.cpp') 330 331 with open(cpphfilename, 'w') as cpphfile: 332 333 jscom.writetopcomment(cpphfile, os.path.split(inputfile)[1], 'Model') 334 guarddefname = 'GEN_JSON_SCHEMA_MODEL__%s_H' % (cppclassname.upper()) 335 336 cpphfile.write(string.Template(""" 337#ifndef ${guarddefname} 338#define ${guarddefname} 339 340#include <ObjectList.h> 341#include <String.h> 342 343""").substitute({'guarddefname': guarddefname})) 344 345 writeheaderincludes(cpphfile, schema['properties']) 346 347 cpphfile.write(string.Template(""" 348class ${cppclassname} { 349public: 350 ${cppclassname}(); 351 virtual ~${cppclassname}(); 352 353 354""").substitute({'cppclassname': cppclassname})) 355 356 for propname, propmetadata in schema['properties'].items(): 357 writeaccessorsheader(cpphfile, propname, propmetadata) 358 cpphfile.write('\n') 359 360 # Now add the instance variables for the object as well. 361 362 cpphfile.write('private:\n') 363 364 for propname, propmetadata in schema['properties'].items(): 365 cpphfile.write(' %s* %s;\n' % ( 366 jscom.propmetadatatocpptypename(propmetadata), 367 jscom.propnametocppmembername(propname))) 368 369 cpphfile.write(( 370 '};\n\n' 371 '#endif // %s' 372 ) % guarddefname) 373 374 with open(cppifilename, 'w') as cppifile: 375 376 jscom.writetopcomment(cppifile, os.path.split(inputfile)[1], 'Model') 377 378 cppifile.write('#include "%s.h"\n' % cppclassname) 379 380 writeconstructor(cppifile, cppclassname, schema) 381 writedestructor(cppifile, cppclassname, schema) 382 383 for propname, propmetadata in schema['properties'].items(): 384 writeaccessors(cppifile, cppclassname, propname, propmetadata) 385 cppifile.write('\n') 386 387 # Now write out any subordinate structures. 388 389 for propname, propmetadata in schema['properties'].items(): 390 jsontype = propmetadata['type'] 391 392 if jsontype == jscom.JSON_TYPE_ARRAY: 393 arraySchema = propmetadata['items'] 394 if arraySchema['type'] == jscom.JSON_TYPE_OBJECT: 395 schematocppmodels(inputfile, arraySchema, outputdirectory) 396 397 if jsontype == jscom.JSON_TYPE_OBJECT: 398 schematocppmodels(inputfile, propmetadata, outputdirectory) 399 400 401def main(): 402 parser = argparse.ArgumentParser(description='Convert JSON schema to Haiku C++ Models') 403 parser.add_argument('-i', '--inputfile', required=True, help='The input filename containing the JSON schema') 404 parser.add_argument('--outputdirectory', help='The output directory where the C++ files should be written') 405 406 args = parser.parse_args() 407 408 outputdirectory = args.outputdirectory 409 410 if not outputdirectory: 411 outputdirectory = '.' 412 413 with open(args.inputfile) as inputfile: 414 schema = json.load(inputfile) 415 schematocppmodels(args.inputfile, schema, outputdirectory) 416 417if __name__ == "__main__": 418 main() 419 420