1#!/usr/bin/python 2 3# ===================================== 4# Copyright 2017-2019, 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'] == '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 List<${cppcontainertype}*, true>(); 42 ${cppmembername}->Add(value); 43} 44 45 46void 47${cppclassname}::Set${cppname}(List<${cppcontainertype}*, true>* 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}(List<${cppcontainertype}*, true>* 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{ 156 return *${cppmembername}; 157} 158 159 160void 161${cppclassname}::Set${cppname}(${cpptype} value) 162{ 163 if (${cppname}IsNull()) 164 ${cppmembername} = new ${cpptype}[1]; 165 ${cppmembername}[0] = value; 166} 167 168 169void 170${cppclassname}::Set${cppname}Null() 171{ 172 if (!${cppname}IsNull()) { 173 delete ${cppmembername}; 174 ${cppmembername} = NULL; 175 } 176} 177 178 179bool 180${cppclassname}::${cppname}IsNull() 181{ 182 return ${cppmembername} == NULL; 183} 184""").substitute(dict)) 185 186 187def writescalaraccessorsheader(outputfile, cppname, cpptype): 188 outputfile.write( 189 string.Template(""" 190 ${cpptype} ${cppname}(); 191 void Set${cppname}(${cpptype} value); 192 void Set${cppname}Null(); 193 bool ${cppname}IsNull(); 194""").substitute({'cppname': cppname, 'cpptype': cpptype})) 195 196 197def writeaccessors(outputfile, cppclassname, propname, propmetadata): 198 type = propmetadata['type'] 199 200 if type == 'array': 201 writelistaccessors(outputfile, 202 cppclassname, 203 jscom.propnametocppname(propname), 204 jscom.propnametocppmembername(propname), 205 jscom.javatypetocppname(propmetadata['items']['javaType'])) 206 elif jscom.propmetadatatypeisscalar(propmetadata): 207 writescalaraccessors(outputfile, 208 cppclassname, 209 jscom.propnametocppname(propname), 210 jscom.propnametocppmembername(propname), 211 jscom.propmetadatatocpptypename(propmetadata)) 212 else: 213 writetakeownershipaccessors(outputfile, 214 cppclassname, 215 jscom.propnametocppname(propname), 216 jscom.propnametocppmembername(propname), 217 jscom.propmetadatatocpptypename(propmetadata)) 218 219 220def writeaccessorsheader(outputfile, propname, propmetadata): 221 type = propmetadata['type'] 222 223 if type == 'array': 224 writelistaccessorsheader(outputfile, 225 jscom.propnametocppname(propname), 226 jscom.javatypetocppname(propmetadata['items']['javaType'])) 227 elif jscom.propmetadatatypeisscalar(propmetadata): 228 writescalaraccessorsheader(outputfile, 229 jscom.propnametocppname(propname), 230 jscom.propmetadatatocpptypename(propmetadata)) 231 else: 232 writetakeownershipaccessorsheader(outputfile, 233 jscom.propnametocppname(propname), 234 jscom.propmetadatatocpptypename(propmetadata)) 235 236 237def writedestructorlogicforlist(outputfile, propname, propmetadata): 238 dict = { 239 'cppmembername': jscom.propnametocppmembername(propname), 240 'cpptype': jscom.javatypetocppname(propmetadata['items']['javaType']) 241 } 242 243 outputfile.write( 244 string.Template(""" int32 count = ${cppmembername}->CountItems(); 245 for (i = 0; i < count; i++) 246 delete ${cppmembername}->ItemAt(i); 247""").substitute(dict)) 248 249 250def writedestructor(outputfile, cppname, schema): 251 outputfile.write('\n\n%s::~%s()\n{\n' % (cppname, cppname)) 252 253 if hasanylistproperties(schema): 254 outputfile.write(' int32 i;\n\n') 255 256 for propname, propmetadata in schema['properties'].items(): 257 propmembername = jscom.propnametocppmembername(propname) 258 259 outputfile.write(' if (%s != NULL) {\n' % propmembername) 260 261 if propmetadata['type'] == 'array': 262 writedestructorlogicforlist(outputfile, propname, propmetadata) 263 264 outputfile.write(( 265 ' delete %s;\n' 266 ) % propmembername) 267 268 outputfile.write(' }\n\n') 269 270 outputfile.write('}\n') 271 272 273def writeconstructor(outputfile, cppname, schema): 274 outputfile.write('\n\n%s::%s()\n{\n' % (cppname, cppname)) 275 276 for propname, propmetadata in schema['properties'].items(): 277 outputfile.write(' %s = NULL;\n' % jscom.propnametocppmembername(propname)) 278 279 outputfile.write('}\n') 280 281 282def writeheaderincludes(outputfile, properties): 283 for propname, propmetadata in properties.items(): 284 jsontype = propmetadata['type'] 285 javatype = None 286 287 if jsontype == 'object': 288 javatype = propmetadata['javaType'] 289 290 if jsontype == 'array': 291 javatype = propmetadata['items']['javaType'] 292 293 if javatype is not None: 294 outputfile.write('#include "%s.h"\n' % jscom.javatypetocppname(javatype)) 295 296 297def schematocppmodels(inputfile, schema, outputdirectory): 298 if schema['type'] != 'object': 299 raise Exception('expecting object') 300 301 javatype = schema['javaType'] 302 303 if not javatype or 0 == len(javatype): 304 raise Exception('missing "javaType" field') 305 306 cppclassname = jscom.javatypetocppname(javatype) 307 cpphfilename = os.path.join(outputdirectory, cppclassname + '.h') 308 cppifilename = os.path.join(outputdirectory, cppclassname + '.cpp') 309 310 with open(cpphfilename, 'w') as cpphfile: 311 312 jscom.writetopcomment(cpphfile, os.path.split(inputfile)[1], 'Model') 313 guarddefname = 'GEN_JSON_SCHEMA_MODEL__%s_H' % (cppclassname.upper()) 314 315 cpphfile.write(string.Template(""" 316#ifndef ${guarddefname} 317#define ${guarddefname} 318 319#include "List.h" 320#include "String.h" 321 322""").substitute({'guarddefname': guarddefname})) 323 324 writeheaderincludes(cpphfile, schema['properties']) 325 326 cpphfile.write(string.Template(""" 327class ${cppclassname} { 328public: 329 ${cppclassname}(); 330 virtual ~${cppclassname}(); 331 332 333""").substitute({'cppclassname': cppclassname})) 334 335 for propname, propmetadata in schema['properties'].items(): 336 writeaccessorsheader(cpphfile, propname, propmetadata) 337 cpphfile.write('\n') 338 339 # Now add the instance variables for the object as well. 340 341 cpphfile.write('private:\n') 342 343 for propname, propmetadata in schema['properties'].items(): 344 cpphfile.write(' %s* %s;\n' % ( 345 jscom.propmetadatatocpptypename(propmetadata), 346 jscom.propnametocppmembername(propname))) 347 348 cpphfile.write(( 349 '};\n\n' 350 '#endif // %s' 351 ) % guarddefname) 352 353 with open(cppifilename, 'w') as cppifile: 354 355 jscom.writetopcomment(cppifile, os.path.split(inputfile)[1], 'Model') 356 357 cppifile.write('#include "%s.h"\n' % cppclassname) 358 359 writeconstructor(cppifile, cppclassname, schema) 360 writedestructor(cppifile, cppclassname, schema) 361 362 for propname, propmetadata in schema['properties'].items(): 363 writeaccessors(cppifile, cppclassname, propname, propmetadata) 364 cppifile.write('\n') 365 366 # Now write out any subordinate structures. 367 368 for propname, propmetadata in schema['properties'].items(): 369 jsontype = propmetadata['type'] 370 371 if jsontype == 'array': 372 schematocppmodels(inputfile, propmetadata['items'], outputdirectory) 373 374 if jsontype == 'object': 375 schematocppmodels(inputfile, propmetadata, outputdirectory) 376 377 378def main(): 379 parser = argparse.ArgumentParser(description='Convert JSON schema to Haiku C++ Models') 380 parser.add_argument('-i', '--inputfile', required=True, help='The input filename containing the JSON schema') 381 parser.add_argument('--outputdirectory', help='The output directory where the C++ files should be written') 382 383 args = parser.parse_args() 384 385 outputdirectory = args.outputdirectory 386 387 if not outputdirectory: 388 outputdirectory = '.' 389 390 with open(args.inputfile) as inputfile: 391 schema = json.load(inputfile) 392 schematocppmodels(args.inputfile, schema, outputdirectory) 393 394if __name__ == "__main__": 395 main() 396 397