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 a 9# listener for use the 'BJson' class in the Haiku system. This 10# allows data conforming to the schema to be able to be parsed. 11 12import string 13import json 14import argparse 15import os 16import hdsjsonschemacommon as jscom 17 18 19# This naming is related to a sub-type in the schema; maybe not the top-level. 20 21class CppParserSubTypeNaming: 22 _cppmodelclassname = None 23 _naming = None 24 25 def __init__(self, schema, naming): 26 type = schema['type'] 27 28 if not type or 0 == len(type): 29 raise Exception('missing "type" field') 30 31 def derivecppmodelclassname(): 32 if type == jscom.JSON_TYPE_OBJECT: 33 javatype = schema['javaType'] 34 if not javatype or 0 == len(javatype): 35 raise Exception('missing "javaType" field') 36 return jscom.javatypetocppname(javatype) 37 if type == jscom.JSON_TYPE_STRING: 38 return jscom.CPP_TYPE_STRING 39 raise Exception('unsupported "type" of "%s"' % type) 40 41 self._cppmodelclassname = derivecppmodelclassname() 42 self._naming = naming 43 44 def cppmodelclassname(self): 45 return self._cppmodelclassname 46 47 def cppstackedlistenerclassname(self): 48 return self._cppmodelclassname + '_' + self._naming.generatejsonlistenername('Stacked') 49 50 def cppstackedlistlistenerclassname(self): 51 if self._cppmodelclassname == jscom.CPP_TYPE_STRING: 52 return self._naming.cppstringliststackedlistenerclassname() 53 return self._cppmodelclassname + '_List_' + self._naming.generatejsonlistenername('Stacked') 54 55 def todict(self): 56 return { 57 'subtype_cppmodelclassname': self.cppmodelclassname(), 58 'subtype_cppstackedlistenerclassname': self.cppstackedlistenerclassname(), 59 'subtype_cppstackedlistlistenerclassname': self.cppstackedlistlistenerclassname() 60 } 61 62 63# This naming relates to the whole schema. It's point of 64# reference is the top level. 65 66class CppParserNaming: 67 _schemaroot = None 68 69 def __init__(self, schemaroot): 70 self._schemaroot = schemaroot 71 72 def cpprootmodelclassname(self): 73 type = self._schemaroot['type'] 74 if type != 'object': 75 raise Exception('expecting object, but was [' + type + "]") 76 77 javatype = self._schemaroot['javaType'] 78 79 if not javatype or 0 == len(javatype): 80 raise Exception('missing "javaType" field') 81 82 return jscom.javatypetocppname(javatype) 83 84 def generatejsonlistenername(self, prefix): 85 return prefix + self.cpprootmodelclassname() + 'JsonListener' 86 87 def cppsupermainlistenerclassname(self): 88 return self.generatejsonlistenername('AbstractMain') 89 90 def cppsinglemainlistenerclassname(self): 91 return self.generatejsonlistenername("Single") 92 93 def cppbulkcontainermainlistenerclassname(self): 94 return self.generatejsonlistenername('BulkContainer') 95 96 def cppsuperstackedlistenerclassname(self): 97 return self.generatejsonlistenername('AbstractStacked') 98 99 def cppstringliststackedlistenerclassname(self): 100 return self.generatejsonlistenername('StringList') 101 102 def cppbulkcontainerstackedlistenerclassname(self): 103 return self.generatejsonlistenername('BulkContainerStacked') 104 105 def cppbulkcontaineritemliststackedlistenerclassname(self): 106 return self.generatejsonlistenername('BulkContainerItemsStacked') 107 108# this is a sub-class of the root object json listener that can call out to the 109# item listener as the root objects are parsed. 110 111 def cppitemlistenerstackedlistenerclassname(self): 112 return self.generatejsonlistenername('ItemEmittingStacked') 113 114 def cppgeneralobjectstackedlistenerclassname(self): 115 return self.generatejsonlistenername('GeneralObjectStacked') 116 117 def cppgeneralarraystackedlistenerclassname(self): 118 return self.generatejsonlistenername('GeneralArrayStacked') 119 120 def cppitemlistenerclassname(self): 121 return self.cpprootmodelclassname() + 'Listener' 122 123 def todict(self): 124 return { 125 'cpprootmodelclassname': self.cpprootmodelclassname(), 126 'cppsupermainlistenerclassname': self.cppsupermainlistenerclassname(), 127 'cppsinglemainlistenerclassname': self.cppsinglemainlistenerclassname(), 128 'cppbulkcontainermainlistenerclassname': self.cppbulkcontainermainlistenerclassname(), 129 'cppbulkcontainerstackedlistenerclassname': self.cppbulkcontainerstackedlistenerclassname(), 130 'cppbulkcontaineritemliststackedlistenerclassname': self.cppbulkcontaineritemliststackedlistenerclassname(), 131 'cppsuperstackedlistenerclassname': self.cppsuperstackedlistenerclassname(), 132 'cppitemlistenerstackedlistenerclassname': self.cppitemlistenerstackedlistenerclassname(), 133 'cppstringliststackedlistenerclassname': self.cppstringliststackedlistenerclassname(), 134 'cppgeneralobjectstackedlistenerclassname': self.cppgeneralobjectstackedlistenerclassname(), 135 'cppgeneralarraystackedlistenerclassname': self.cppgeneralarraystackedlistenerclassname(), 136 'cppitemlistenerclassname': self.cppitemlistenerclassname() 137 } 138 139 140class CppParserImplementationState: 141 142 _interfacehandledcppnames = [] 143 _implementationhandledcppnames = [] 144 _outputfile = None 145 _naming = None 146 147 def __init__(self, outputfile, naming): 148 self._outputfile = outputfile 149 self._naming = naming 150 151 def isinterfacehandledcppname(self, name): 152 return name in self._interfacehandledcppnames 153 154 def addinterfacehandledcppname(self, name): 155 self._interfacehandledcppnames.append(name) 156 157 def isimplementationhandledcppname(self, name): 158 return name in self._implementationhandledcppnames 159 160 def addimplementationhandledcppname(self, name): 161 self._implementationhandledcppnames.append(name) 162 163 def naming(self): 164 return self._naming 165 166 def outputfile(self): 167 return self._outputfile 168 169 170def writerootstackedlistenerinterface(istate): 171 istate.outputfile().write( 172 string.Template(""" 173/*! This class is the top level of the stacked listeners. The stack structure 174 is maintained in a linked list and sub-classes implement specific behaviors 175 depending where in the parse tree the stacked listener is working at. 176*/ 177class ${cppsuperstackedlistenerclassname} : public BJsonEventListener { 178public: 179 ${cppsuperstackedlistenerclassname}( 180 ${cppsupermainlistenerclassname}* mainListener, 181 ${cppsuperstackedlistenerclassname}* parent); 182 ~${cppsuperstackedlistenerclassname}(); 183 184 void HandleError(status_t status, int32 line, const char* message); 185 void Complete(); 186 187 status_t ErrorStatus(); 188 189 ${cppsuperstackedlistenerclassname}* Parent(); 190 191 virtual bool WillPop(); 192 193protected: 194 ${cppsupermainlistenerclassname}* fMainListener; 195 196 bool Pop(); 197 void Push(${cppsuperstackedlistenerclassname}* stackedListener); 198 199 200private: 201 ${cppsuperstackedlistenerclassname}* fParent; 202}; 203""").substitute(istate.naming().todict())) 204 205 206def writerootstackedlistenerimplementation(istate): 207 istate.outputfile().write( 208 string.Template(""" 209${cppsuperstackedlistenerclassname}::${cppsuperstackedlistenerclassname} ( 210 ${cppsupermainlistenerclassname}* mainListener, 211 ${cppsuperstackedlistenerclassname}* parent) 212{ 213 fMainListener = mainListener; 214 fParent = parent; 215} 216 217${cppsuperstackedlistenerclassname}::~${cppsuperstackedlistenerclassname}() 218{ 219} 220 221void 222${cppsuperstackedlistenerclassname}::HandleError(status_t status, int32 line, const char* message) 223{ 224 fMainListener->HandleError(status, line, message); 225} 226 227void 228${cppsuperstackedlistenerclassname}::Complete() 229{ 230 fMainListener->Complete(); 231} 232 233status_t 234${cppsuperstackedlistenerclassname}::ErrorStatus() 235{ 236 return fMainListener->ErrorStatus(); 237} 238 239${cppsuperstackedlistenerclassname}* 240${cppsuperstackedlistenerclassname}::Parent() 241{ 242 return fParent; 243} 244 245void 246${cppsuperstackedlistenerclassname}::Push(${cppsuperstackedlistenerclassname}* stackedListener) 247{ 248 fMainListener->SetStackedListener(stackedListener); 249} 250 251bool 252${cppsuperstackedlistenerclassname}::WillPop() 253{ 254 return true; 255} 256 257bool 258${cppsuperstackedlistenerclassname}::Pop() 259{ 260 bool result = WillPop(); 261 fMainListener->SetStackedListener(fParent); 262 return result; 263} 264""").substitute(istate.naming().todict())) 265 266 267def writestringliststackedlistenerinterface(istate): 268 istate.outputfile().write( 269 string.Template(""" 270 271/*! Sometimes attributes of objects are able to be arrays of strings. This 272 listener will parse and return the array of strings. 273*/ 274 275class ${cppstringliststackedlistenerclassname} : public ${cppsuperstackedlistenerclassname} { 276public: 277 ${cppstringliststackedlistenerclassname}( 278 ${cppsupermainlistenerclassname}* mainListener, 279 ${cppsuperstackedlistenerclassname}* parent); 280 ~${cppstringliststackedlistenerclassname}(); 281 282 bool Handle(const BJsonEvent& event); 283 BObjectList<BString>* Target(); 284 285protected: 286 BObjectList<BString>* fTarget; 287}; 288""").substitute(istate.naming().todict())) 289 290 291def writestringliststackedlistenerimplementation(istate): 292 istate.outputfile().write( 293 string.Template(""" 294${cppstringliststackedlistenerclassname}::${cppstringliststackedlistenerclassname}( 295 ${cppsupermainlistenerclassname}* mainListener, 296 ${cppsuperstackedlistenerclassname}* parent) 297 : 298 ${cppsuperstackedlistenerclassname}(mainListener, parent) 299{ 300 fTarget = new BObjectList<BString>(); 301} 302 303 304${cppstringliststackedlistenerclassname}::~${cppstringliststackedlistenerclassname}() 305{ 306} 307 308 309BObjectList<BString>* 310${cppstringliststackedlistenerclassname}::Target() 311{ 312 return fTarget; 313} 314 315 316bool 317${cppstringliststackedlistenerclassname}::Handle(const BJsonEvent& event) 318{ 319 switch (event.EventType()) { 320 case B_JSON_ARRAY_END: 321 { 322 bool status = Pop() && (ErrorStatus() == B_OK); 323 delete this; 324 return status; 325 } 326 case B_JSON_STRING: 327 { 328 fTarget->AddItem(new BString(event.Content())); 329 break; 330 } 331 default: 332 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, 333 "illegal state - unexpected json event parsing a string array"); 334 break; 335 } 336 337 return ErrorStatus() == B_OK; 338} 339""").substitute(istate.naming().todict())) 340 341 342def writeageneralstackedlistenerinterface(istate, alistenerclassname): 343 istate.outputfile().write( 344 string.Template(""" 345class ${alistenerclassname} : public ${cppsuperstackedlistenerclassname} { 346public: 347 ${alistenerclassname}( 348 ${cppsupermainlistenerclassname}* mainListener, 349 ${cppsuperstackedlistenerclassname}* parent); 350 ~${alistenerclassname}(); 351 352 353 bool Handle(const BJsonEvent& event); 354}; 355""").substitute(jscom.uniondicts( 356 istate.naming().todict(), 357 {'alistenerclassname': alistenerclassname} 358 ))) 359 360 361def writegeneralstackedlistenerinterface(istate): 362 writeageneralstackedlistenerinterface(istate, istate.naming().cppgeneralarraystackedlistenerclassname()) 363 writeageneralstackedlistenerinterface(istate, istate.naming().cppgeneralobjectstackedlistenerclassname()) 364 365 366def writegeneralnoopstackedlistenerconstructordestructor(istate, aclassname): 367 368 istate.outputfile().write( 369 string.Template(""" 370${aclassname}::${aclassname}( 371 ${cppsupermainlistenerclassname}* mainListener, 372 ${cppsuperstackedlistenerclassname}* parent) 373 : 374 ${cppsuperstackedlistenerclassname}(mainListener, parent) 375{ 376} 377 378${aclassname}::~${aclassname}() 379{ 380} 381""").substitute(jscom.uniondicts( 382 istate.naming().todict(), 383 {'aclassname': aclassname})) 384 ) 385 386 387def writegeneralstackedlistenerimplementation(istate): 388 outfile = istate.outputfile() 389 generalobjectclassname = istate.naming().cppgeneralobjectstackedlistenerclassname() 390 generalarrayclassname = istate.naming().cppgeneralarraystackedlistenerclassname() 391 substitutedict = { 392 'generalobjectclassname': generalobjectclassname, 393 'generalarrayclassname': generalarrayclassname 394 } 395 396# general object consumer that will parse-and-discard any json objects. 397 398 writegeneralnoopstackedlistenerconstructordestructor(istate, generalobjectclassname) 399 400 istate.outputfile().write( 401 string.Template(""" 402bool 403${generalobjectclassname}::Handle(const BJsonEvent& event) 404{ 405 switch (event.EventType()) { 406 case B_JSON_OBJECT_NAME: 407 case B_JSON_NUMBER: 408 case B_JSON_STRING: 409 case B_JSON_TRUE: 410 case B_JSON_FALSE: 411 case B_JSON_NULL: 412 // ignore 413 break; 414 case B_JSON_OBJECT_START: 415 Push(new ${generalobjectclassname}(fMainListener, this)); 416 break; 417 case B_JSON_ARRAY_START: 418 Push(new ${generalarrayclassname}(fMainListener, this)); 419 break; 420 case B_JSON_ARRAY_END: 421 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected end of array"); 422 break; 423 case B_JSON_OBJECT_END: 424 { 425 bool status = Pop() && (ErrorStatus() == B_OK); 426 delete this; 427 return status; 428 } 429 } 430 431 return ErrorStatus() == B_OK; 432} 433""").substitute(substitutedict)) 434 435 # general array consumer that will parse-and-discard any json arrays. 436 437 writegeneralnoopstackedlistenerconstructordestructor(istate, generalarrayclassname) 438 439 outfile.write( 440 string.Template(""" 441bool 442${generalarrayclassname}::Handle(const BJsonEvent& event) 443{ 444 switch (event.EventType()) { 445 case B_JSON_OBJECT_NAME: 446 case B_JSON_NUMBER: 447 case B_JSON_STRING: 448 case B_JSON_TRUE: 449 case B_JSON_FALSE: 450 case B_JSON_NULL: 451 // ignore 452 break; 453 case B_JSON_OBJECT_START: 454 Push(new ${generalobjectclassname}(fMainListener, this)); 455 break; 456 case B_JSON_ARRAY_START: 457 Push(new ${generalarrayclassname}(fMainListener, this)); 458 break; 459 case B_JSON_OBJECT_END: 460 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected end of object"); 461 break; 462 case B_JSON_ARRAY_END: 463 { 464 bool status = Pop() && (ErrorStatus() == B_OK); 465 delete this; 466 return status; 467 } 468 } 469 470 471 return ErrorStatus() == B_OK; 472} 473""").substitute(substitutedict)) 474 475 476def writestackedlistenerinterface(istate, subschema): 477 naming = istate.naming() 478 subtypenaming = CppParserSubTypeNaming(subschema, naming) 479 480 if not istate.isinterfacehandledcppname(subtypenaming.cppmodelclassname()): 481 istate.addinterfacehandledcppname(subtypenaming.cppmodelclassname()) 482 483 istate.outputfile().write( 484 string.Template(""" 485class ${subtype_cppstackedlistenerclassname} : public ${cppsuperstackedlistenerclassname} { 486public: 487 ${subtype_cppstackedlistenerclassname}( 488 ${cppsupermainlistenerclassname}* mainListener, 489 ${cppsuperstackedlistenerclassname}* parent); 490 ~${subtype_cppstackedlistenerclassname}(); 491 492 493 bool Handle(const BJsonEvent& event); 494 495 496 ${subtype_cppmodelclassname}* Target(); 497 498 499protected: 500 ${subtype_cppmodelclassname}* fTarget; 501 BString fNextItemName; 502}; 503 504class ${subtype_cppstackedlistlistenerclassname} : public ${cppsuperstackedlistenerclassname} { 505public: 506 ${subtype_cppstackedlistlistenerclassname}( 507 ${cppsupermainlistenerclassname}* mainListener, 508 ${cppsuperstackedlistenerclassname}* parent); 509 ~${subtype_cppstackedlistlistenerclassname}(); 510 511 bool Handle(const BJsonEvent& event); 512 513 BObjectList<${subtype_cppmodelclassname}>* Target(); 514 // list of ${subtype_cppmodelclassname} pointers 515 516private: 517 BObjectList<${subtype_cppmodelclassname}>* fTarget; 518}; 519""").substitute(jscom.uniondicts(naming.todict(), subtypenaming.todict()))) 520 521 if 'properties' in subschema: 522 523 for propname, propmetadata in subschema['properties'].items(): 524 if propmetadata['type'] == jscom.JSON_TYPE_ARRAY: 525 if propmetadata['items']['type'] == jscom.JSON_TYPE_OBJECT: 526 writestackedlistenerinterface(istate, propmetadata['items']) 527 elif propmetadata['type'] == jscom.JSON_TYPE_OBJECT: 528 writestackedlistenerinterface(istate, propmetadata) 529 530 531def writebulkcontainerstackedlistenerinterface(istate, schema): 532 naming = istate.naming() 533 subtypenaming = CppParserSubTypeNaming(schema, naming) 534 outfile = istate.outputfile() 535 536# This is a sub-class of the main model object listener. It will ping out to an item listener 537# when parsing is complete. 538 539 outfile.write( 540 string.Template(""" 541class ${cppitemlistenerstackedlistenerclassname} : public ${subtype_cppstackedlistenerclassname} { 542public: 543 ${cppitemlistenerstackedlistenerclassname}( 544 ${cppsupermainlistenerclassname}* mainListener, 545 ${cppsuperstackedlistenerclassname}* parent, 546 ${cppitemlistenerclassname}* itemListener); 547 ~${cppitemlistenerstackedlistenerclassname}(); 548 549 550 bool WillPop(); 551 552 553private: 554 ${cppitemlistenerclassname}* fItemListener; 555}; 556 557 558class ${cppbulkcontainerstackedlistenerclassname} : public ${cppsuperstackedlistenerclassname} { 559public: 560 ${cppbulkcontainerstackedlistenerclassname}( 561 ${cppsupermainlistenerclassname}* mainListener, 562 ${cppsuperstackedlistenerclassname}* parent, 563 ${cppitemlistenerclassname}* itemListener); 564 ~${cppbulkcontainerstackedlistenerclassname}(); 565 566 567 bool Handle(const BJsonEvent& event); 568 569 570private: 571 BString fNextItemName; 572 ${cppitemlistenerclassname}* fItemListener; 573}; 574 575 576class ${cppbulkcontaineritemliststackedlistenerclassname} : public ${cppsuperstackedlistenerclassname} { 577public: 578 ${cppbulkcontaineritemliststackedlistenerclassname}( 579 ${cppsupermainlistenerclassname}* mainListener, 580 ${cppsuperstackedlistenerclassname}* parent, 581 ${cppitemlistenerclassname}* itemListener); 582 ~${cppbulkcontaineritemliststackedlistenerclassname}(); 583 584 585 bool Handle(const BJsonEvent& event); 586 bool WillPop(); 587 588 589private: 590 ${cppitemlistenerclassname}* fItemListener; 591}; 592""").substitute(jscom.uniondicts(naming.todict(), subtypenaming.todict()))) 593 594 595def writestackedlistenerfieldimplementation( 596 istate, 597 propname, 598 cppeventdataexpression): 599 600 istate.outputfile().write( 601 string.Template(""" 602 if (fNextItemName == "${propname}") 603 fTarget->Set${cpppropname}(${cppeventdataexpression}); 604 """).substitute({ 605 'propname': propname, 606 'cpppropname': jscom.propnametocppname(propname), 607 'cppeventdataexpression': cppeventdataexpression 608 })) 609 610 611def writenullstackedlistenerfieldimplementation( 612 istate, 613 propname): 614 615 istate.outputfile().write( 616 string.Template(""" 617 if (fNextItemName == "${propname}") 618 fTarget->Set${cpppropname}Null(); 619 """).substitute({ 620 'propname': propname, 621 'cpppropname': jscom.propnametocppname(propname) 622 })) 623 624 625def writestackedlistenerfieldsimplementation( 626 istate, 627 schema, 628 selectedcpptypename, 629 jsoneventtypename, 630 cppeventdataexpression): 631 632 outfile = istate.outputfile() 633 634 outfile.write(' case ' + jsoneventtypename + ':\n') 635 636 for propname, propmetadata in schema['properties'].items(): 637 cpptypename = jscom.propmetadatatocpptypename(propmetadata) 638 639 if cpptypename == selectedcpptypename: 640 writestackedlistenerfieldimplementation(istate, propname, cppeventdataexpression) 641 642 outfile.write(' fNextItemName.SetTo("");\n') 643 outfile.write(' break;\n') 644 645 646def writestackedlistenertypedobjectimplementation(istate, schema): 647 outfile = istate.outputfile() 648 naming = istate.naming(); 649 subtypenaming = CppParserSubTypeNaming(schema, naming) 650 651 outfile.write( 652 string.Template(""" 653${subtype_cppstackedlistenerclassname}::${subtype_cppstackedlistenerclassname}( 654 ${cppsupermainlistenerclassname}* mainListener, 655 ${cppsuperstackedlistenerclassname}* parent) 656 : 657 ${cppsuperstackedlistenerclassname}(mainListener, parent) 658{ 659 fTarget = new ${subtype_cppmodelclassname}(); 660} 661 662 663${subtype_cppstackedlistenerclassname}::~${subtype_cppstackedlistenerclassname}() 664{ 665} 666 667 668${subtype_cppmodelclassname}* 669${subtype_cppstackedlistenerclassname}::Target() 670{ 671 return fTarget; 672} 673 674 675bool 676${subtype_cppstackedlistenerclassname}::Handle(const BJsonEvent& event) 677{ 678 switch (event.EventType()) { 679 case B_JSON_ARRAY_END: 680 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected start of array"); 681 break; 682 case B_JSON_OBJECT_NAME: 683 fNextItemName = event.Content(); 684 break; 685 case B_JSON_OBJECT_END: 686 { 687 bool status = Pop() && (ErrorStatus() == B_OK); 688 delete this; 689 return status; 690 } 691 692""").substitute(jscom.uniondicts( 693 naming.todict(), 694 subtypenaming.todict()))) 695 696 # now extract the fields from the schema that need to be fed in. 697 698 writestackedlistenerfieldsimplementation( 699 istate, schema, 700 jscom.CPP_TYPE_STRING, 'B_JSON_STRING', 'new BString(event.Content())') 701 702 writestackedlistenerfieldsimplementation( 703 istate, schema, 704 jscom.CPP_TYPE_BOOLEAN, 'B_JSON_TRUE', 'true') 705 706 writestackedlistenerfieldsimplementation( 707 istate, schema, 708 jscom.CPP_TYPE_BOOLEAN, 'B_JSON_FALSE', 'false') 709 710 outfile.write(' case B_JSON_NULL:\n') 711 outfile.write(' {\n') 712 713 for propname, propmetadata in schema['properties'].items(): 714 # TODO; deal with array case somehow. 715 if 'array' != propmetadata['type']: 716 writenullstackedlistenerfieldimplementation(istate, propname) 717 outfile.write(' fNextItemName.SetTo("");\n') 718 outfile.write(' break;\n') 719 outfile.write(' }\n') 720 721 # number type is a bit complex because it can either be a double or it can be an 722 # integral value. 723 724 outfile.write(' case B_JSON_NUMBER:\n') 725 outfile.write(' {\n') 726 727 for propname, propmetadata in schema['properties'].items(): 728 propcpptypename = jscom.propmetadatatocpptypename(propmetadata) 729 if propcpptypename == jscom.CPP_TYPE_INTEGER: 730 writestackedlistenerfieldimplementation(istate, propname, 'event.ContentInteger()') 731 elif propcpptypename == jscom.CPP_TYPE_NUMBER: 732 writestackedlistenerfieldimplementation(istate, propname, 'event.ContentDouble()') 733 outfile.write(' fNextItemName.SetTo("");\n') 734 outfile.write(' break;\n') 735 outfile.write(' }\n') 736 737 # object type; could be a sub-type or otherwise just drop into a placebo consumer to keep the parse 738 # structure working. This would most likely be additional sub-objects that are additional to the 739 # expected schema. 740 741 outfile.write(' case B_JSON_OBJECT_START:\n') 742 outfile.write(' {\n') 743 744 objectifclausekeyword = 'if' 745 746 for propname, propmetadata in schema['properties'].items(): 747 if propmetadata['type'] == jscom.JSON_TYPE_OBJECT: 748 subtypenaming = CppParserSubTypeNaming(propmetadata, naming) 749 750 outfile.write(' %s (fNextItemName == "%s") {\n' % (objectifclausekeyword, propname)) 751 outfile.write(' %s* nextListener = new %s(fMainListener, this);\n' % ( 752 subtypenaming.cppstackedlistenerclassname(), 753 subtypenaming.cppstackedlistenerclassname())) 754 outfile.write(' fTarget->Set%s(nextListener->Target());\n' % ( 755 subtypenaming.cppmodelclassname())) 756 outfile.write(' Push(nextListener);\n') 757 outfile.write(' }\n') 758 759 objectifclausekeyword = 'else if' 760 761 outfile.write(' %s (1 == 1) {\n' % objectifclausekeyword) 762 outfile.write(' %s* nextListener = new %s(fMainListener, this);\n' % ( 763 naming.cppgeneralobjectstackedlistenerclassname(), 764 naming.cppgeneralobjectstackedlistenerclassname())) 765 outfile.write(' Push(nextListener);\n') 766 outfile.write(' }\n') 767 outfile.write(' fNextItemName.SetTo("");\n') 768 outfile.write(' break;\n') 769 outfile.write(' }\n') 770 771 # array type; could be an array of objects or otherwise just drop into a placebo consumer to keep 772 # the parse structure working. 773 774 outfile.write(' case B_JSON_ARRAY_START:\n') 775 outfile.write(' {\n') 776 777 objectifclausekeyword = 'if' 778 779 for propname, propmetadata in schema['properties'].items(): 780 if propmetadata['type'] == jscom.JSON_TYPE_ARRAY: 781 subtypenaming = CppParserSubTypeNaming(propmetadata['items'], naming) 782 783 outfile.write(' %s (fNextItemName == "%s") {\n' % (objectifclausekeyword, propname)) 784 outfile.write(' %s* nextListener = new %s(fMainListener, this);\n' % ( 785 subtypenaming.cppstackedlistlistenerclassname(), 786 subtypenaming.cppstackedlistlistenerclassname())) 787 outfile.write(' fTarget->Set%s(nextListener->Target());\n' % ( 788 jscom.propnametocppname(propname))) 789 outfile.write(' Push(nextListener);\n') 790 outfile.write(' }\n') 791 792 objectifclausekeyword = 'else if' 793 794 outfile.write(' %s (1 == 1) {\n' % objectifclausekeyword) 795 outfile.write(' %s* nextListener = new %s(fMainListener, this);\n' % ( 796 naming.cppsuperstackedlistenerclassname(), 797 naming.cppgeneralarraystackedlistenerclassname())) 798 outfile.write(' Push(nextListener);\n') 799 outfile.write(' }\n') 800 outfile.write(' fNextItemName.SetTo("");\n') 801 outfile.write(' break;\n') 802 outfile.write(' }\n') 803 804 outfile.write(""" 805 } 806 807 808 return ErrorStatus() == B_OK; 809} 810""") 811 812 813def writestackedlistenertypedobjectlistimplementation(istate, schema): 814 naming = istate.naming() 815 subtypenaming = CppParserSubTypeNaming(schema, naming) 816 outfile = istate.outputfile() 817 818 outfile.write( 819 string.Template(""" 820${subtype_cppstackedlistlistenerclassname}::${subtype_cppstackedlistlistenerclassname}( 821 ${cppsupermainlistenerclassname}* mainListener, 822 ${cppsuperstackedlistenerclassname}* parent) 823 : 824 ${cppsuperstackedlistenerclassname}(mainListener, parent) 825{ 826 fTarget = new BObjectList<${subtype_cppmodelclassname}>(); 827} 828 829 830${subtype_cppstackedlistlistenerclassname}::~${subtype_cppstackedlistlistenerclassname}() 831{ 832} 833 834 835BObjectList<${subtype_cppmodelclassname}>* 836${subtype_cppstackedlistlistenerclassname}::Target() 837{ 838 return fTarget; 839} 840 841 842bool 843${subtype_cppstackedlistlistenerclassname}::Handle(const BJsonEvent& event) 844{ 845 switch (event.EventType()) { 846 case B_JSON_ARRAY_END: 847 { 848 bool status = Pop() && (ErrorStatus() == B_OK); 849 delete this; 850 return status; 851 } 852 case B_JSON_OBJECT_START: 853 { 854 ${subtype_cppstackedlistenerclassname}* nextListener = 855 new ${subtype_cppstackedlistenerclassname}(fMainListener, this); 856 fTarget->AddItem(nextListener->Target()); 857 Push(nextListener); 858 break; 859 } 860 default: 861 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, 862 "illegal state - unexpected json event parsing an array of ${subtype_cppmodelclassname}"); 863 break; 864 } 865 866 return ErrorStatus() == B_OK; 867} 868""").substitute(jscom.uniondicts(naming.todict(), subtypenaming.todict()))) 869 870 871def writebulkcontainerstackedlistenerimplementation(istate, schema): 872 naming = istate.naming() 873 subtypenaming = CppParserSubTypeNaming(schema, naming) 874 outfile = istate.outputfile() 875 876 outfile.write( 877 string.Template(""" 878${cppitemlistenerstackedlistenerclassname}::${cppitemlistenerstackedlistenerclassname}( 879 ${cppsupermainlistenerclassname}* mainListener, ${cppsuperstackedlistenerclassname}* parent, 880 ${cppitemlistenerclassname}* itemListener) 881: 882${subtype_cppstackedlistenerclassname}(mainListener, parent) 883{ 884 fItemListener = itemListener; 885} 886 887 888${cppitemlistenerstackedlistenerclassname}::~${cppitemlistenerstackedlistenerclassname}() 889{ 890} 891 892 893bool 894${cppitemlistenerstackedlistenerclassname}::WillPop() 895{ 896 bool result = fItemListener->Handle(fTarget); 897 delete fTarget; 898 fTarget = NULL; 899 return result; 900} 901 902 903${cppbulkcontainerstackedlistenerclassname}::${cppbulkcontainerstackedlistenerclassname}( 904 ${cppsupermainlistenerclassname}* mainListener, ${cppsuperstackedlistenerclassname}* parent, 905 ${cppitemlistenerclassname}* itemListener) 906: 907${cppsuperstackedlistenerclassname}(mainListener, parent) 908{ 909 fItemListener = itemListener; 910} 911 912 913${cppbulkcontainerstackedlistenerclassname}::~${cppbulkcontainerstackedlistenerclassname}() 914{ 915} 916 917 918bool 919${cppbulkcontainerstackedlistenerclassname}::Handle(const BJsonEvent& event) 920{ 921 switch (event.EventType()) { 922 case B_JSON_ARRAY_END: 923 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected start of array"); 924 break; 925 case B_JSON_OBJECT_NAME: 926 fNextItemName = event.Content(); 927 break; 928 case B_JSON_OBJECT_START: 929 Push(new ${cppgeneralobjectstackedlistenerclassname}(fMainListener, this)); 930 break; 931 case B_JSON_ARRAY_START: 932 if (fNextItemName == "items") 933 Push(new ${cppbulkcontaineritemliststackedlistenerclassname}(fMainListener, this, fItemListener)); 934 else 935 Push(new ${cppgeneralarraystackedlistenerclassname}(fMainListener, this)); 936 break; 937 case B_JSON_OBJECT_END: 938 { 939 bool status = Pop() && (ErrorStatus() == B_OK); 940 delete this; 941 return status; 942 } 943 default: 944 // ignore 945 break; 946 } 947 948 return ErrorStatus() == B_OK; 949} 950 951 952${cppbulkcontaineritemliststackedlistenerclassname}::${cppbulkcontaineritemliststackedlistenerclassname}( 953 ${cppsupermainlistenerclassname}* mainListener, ${cppsuperstackedlistenerclassname}* parent, 954 ${cppitemlistenerclassname}* itemListener) 955: 956${cppsuperstackedlistenerclassname}(mainListener, parent) 957{ 958 fItemListener = itemListener; 959} 960 961 962${cppbulkcontaineritemliststackedlistenerclassname}::~${cppbulkcontaineritemliststackedlistenerclassname}() 963{ 964} 965 966 967bool 968${cppbulkcontaineritemliststackedlistenerclassname}::Handle(const BJsonEvent& event) 969{ 970 switch (event.EventType()) { 971 case B_JSON_OBJECT_START: 972 Push(new ${cppitemlistenerstackedlistenerclassname}(fMainListener, this, fItemListener)); 973 break; 974 case B_JSON_ARRAY_END: 975 { 976 bool status = Pop() && (ErrorStatus() == B_OK); 977 delete this; 978 return status; 979 } 980 default: 981 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, "illegal state - unexpected json event"); 982 break; 983 } 984 985 return ErrorStatus() == B_OK; 986} 987 988 989bool 990${cppbulkcontaineritemliststackedlistenerclassname}::WillPop() 991{ 992 fItemListener->Complete(); 993 return true; 994} 995 996 997""").substitute(jscom.uniondicts(naming.todict(), subtypenaming.todict()))) 998 999 1000def writestackedlistenerimplementation(istate, schema): 1001 subtypenaming = CppParserSubTypeNaming(schema, istate.naming()) 1002 1003 if not istate.isimplementationhandledcppname(subtypenaming.cppmodelclassname()): 1004 istate.addimplementationhandledcppname(subtypenaming.cppmodelclassname()) 1005 1006 writestackedlistenertypedobjectimplementation(istate, schema) 1007 writestackedlistenertypedobjectlistimplementation(istate, schema) # TODO; only if necessary. 1008 1009 # now create the parser types for any subordinate objects descending. 1010 1011 for propname, propmetadata in schema['properties'].items(): 1012 if propmetadata['type'] == 'array': 1013 items = propmetadata['items'] 1014 if items['type'] == jscom.JSON_TYPE_OBJECT: 1015 writestackedlistenerimplementation(istate, items) 1016 elif propmetadata['type'] == 'object': 1017 writestackedlistenerimplementation(istate, propmetadata) 1018 1019 1020def writemainlistenerimplementation(istate, schema, supportbulkcontainer): 1021 outfile = istate.outputfile() 1022 naming = istate.naming() 1023 subtypenaming = CppParserSubTypeNaming(schema, istate.naming()) 1024 1025# super (abstract) listener 1026 1027 outfile.write( 1028 string.Template(""" 1029${cppsupermainlistenerclassname}::${cppsupermainlistenerclassname}() 1030{ 1031 fStackedListener = NULL; 1032 fErrorStatus = B_OK; 1033} 1034 1035 1036${cppsupermainlistenerclassname}::~${cppsupermainlistenerclassname}() 1037{ 1038} 1039 1040 1041void 1042${cppsupermainlistenerclassname}::HandleError(status_t status, int32 line, const char* message) 1043{ 1044 if (message != NULL) { 1045 fprintf(stderr, "an error has arisen processing json for '${cpprootmodelclassname}'; %s\\n", message); 1046 } else { 1047 fprintf(stderr, "an error has arisen processing json for '${cpprootmodelclassname}'\\n"); 1048 } 1049 fErrorStatus = status; 1050} 1051 1052 1053void 1054${cppsupermainlistenerclassname}::Complete() 1055{ 1056} 1057 1058 1059status_t 1060${cppsupermainlistenerclassname}::ErrorStatus() 1061{ 1062 return fErrorStatus; 1063} 1064 1065void 1066${cppsupermainlistenerclassname}::SetStackedListener( 1067 ${cppsuperstackedlistenerclassname}* stackedListener) 1068{ 1069 fStackedListener = stackedListener; 1070} 1071 1072""").substitute(naming.todict())) 1073 1074# single parser 1075 1076 outfile.write( 1077 string.Template(""" 1078${cppsinglemainlistenerclassname}::${cppsinglemainlistenerclassname}() 1079: 1080${cppsupermainlistenerclassname}() 1081{ 1082 fTarget = NULL; 1083} 1084 1085 1086${cppsinglemainlistenerclassname}::~${cppsinglemainlistenerclassname}() 1087{ 1088} 1089 1090 1091bool 1092${cppsinglemainlistenerclassname}::Handle(const BJsonEvent& event) 1093{ 1094 if (fErrorStatus != B_OK) 1095 return false; 1096 1097 if (fStackedListener != NULL) 1098 return fStackedListener->Handle(event); 1099 1100 switch (event.EventType()) { 1101 case B_JSON_OBJECT_START: 1102 { 1103 ${subtype_cppstackedlistenerclassname}* nextListener = new ${subtype_cppstackedlistenerclassname}( 1104 this, NULL); 1105 fTarget = nextListener->Target(); 1106 SetStackedListener(nextListener); 1107 break; 1108 } 1109 default: 1110 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, 1111 "illegal state - unexpected json event parsing top level for ${cpprootmodelclassname}"); 1112 break; 1113 } 1114 1115 1116 return ErrorStatus() == B_OK; 1117} 1118 1119 1120${cpprootmodelclassname}* 1121${cppsinglemainlistenerclassname}::Target() 1122{ 1123 return fTarget; 1124} 1125 1126""").substitute(jscom.uniondicts(naming.todict(), subtypenaming.todict()))) 1127 1128 if supportbulkcontainer: 1129 1130 # create a main listener that can work through the list of top level model objects and ping the listener 1131 1132 outfile.write( 1133 string.Template(""" 1134${cppbulkcontainermainlistenerclassname}::${cppbulkcontainermainlistenerclassname}( 1135 ${cppitemlistenerclassname}* itemListener) : ${cppsupermainlistenerclassname}() 1136{ 1137 fItemListener = itemListener; 1138} 1139 1140 1141${cppbulkcontainermainlistenerclassname}::~${cppbulkcontainermainlistenerclassname}() 1142{ 1143} 1144 1145 1146bool 1147${cppbulkcontainermainlistenerclassname}::Handle(const BJsonEvent& event) 1148{ 1149 if (fErrorStatus != B_OK) 1150 return false; 1151 1152 if (fStackedListener != NULL) 1153 return fStackedListener->Handle(event); 1154 1155 switch (event.EventType()) { 1156 case B_JSON_OBJECT_START: 1157 { 1158 ${cppbulkcontainerstackedlistenerclassname}* nextListener = 1159 new ${cppbulkcontainerstackedlistenerclassname}( 1160 this, NULL, fItemListener); 1161 SetStackedListener(nextListener); 1162 return true; 1163 break; 1164 } 1165 default: 1166 HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, 1167 "illegal state - unexpected json event parsing top level for ${cppbulkcontainermainlistenerclassname}"); 1168 break; 1169 } 1170 1171 1172 return ErrorStatus() == B_OK; 1173} 1174 1175""").substitute(jscom.uniondicts(naming.todict(), subtypenaming.todict()))) 1176 1177 1178def schematocppparser(inputfile, schema, outputdirectory, supportbulkcontainer): 1179 naming = CppParserNaming(schema) 1180 cppheaderleafname = naming.cpprootmodelclassname() + 'JsonListener.h' 1181 cppheaderfilename = os.path.join(outputdirectory, cppheaderleafname) 1182 cppimplementationfilename = os.path.join(outputdirectory, naming.cpprootmodelclassname() + 'JsonListener.cpp') 1183 1184 with open(cppheaderfilename, 'w') as cpphfile: 1185 jscom.writetopcomment(cpphfile, os.path.split(inputfile)[1], 'Listener') 1186 guarddefname = 'GEN_JSON_SCHEMA_PARSER__%s_H' % (naming.cppsinglemainlistenerclassname().upper()) 1187 1188 cpphfile.write( 1189 string.Template(""" 1190#ifndef ${guarddefname} 1191#define ${guarddefname} 1192""").substitute({'guarddefname': guarddefname})) 1193 1194 cpphfile.write( 1195 string.Template(""" 1196#include <JsonEventListener.h> 1197#include <ObjectList.h> 1198 1199#include "${cpprootmodelclassname}.h" 1200 1201class ${cppsuperstackedlistenerclassname}; 1202 1203class ${cppsupermainlistenerclassname} : public BJsonEventListener { 1204friend class ${cppsuperstackedlistenerclassname}; 1205public: 1206 ${cppsupermainlistenerclassname}(); 1207 virtual ~${cppsupermainlistenerclassname}(); 1208 1209 void HandleError(status_t status, int32 line, const char* message); 1210 void Complete(); 1211 status_t ErrorStatus(); 1212 1213protected: 1214 void SetStackedListener( 1215 ${cppsuperstackedlistenerclassname}* listener); 1216 status_t fErrorStatus; 1217 ${cppsuperstackedlistenerclassname}* fStackedListener; 1218}; 1219 1220 1221/*! Use this listener when you want to parse some JSON data that contains 1222 just a single instance of ${cpprootmodelclassname}. 1223*/ 1224class ${cppsinglemainlistenerclassname} 1225 : public ${cppsupermainlistenerclassname} { 1226friend class ${cppsuperstackedlistenerclassname}; 1227public: 1228 ${cppsinglemainlistenerclassname}(); 1229 virtual ~${cppsinglemainlistenerclassname}(); 1230 1231 bool Handle(const BJsonEvent& event); 1232 ${cpprootmodelclassname}* Target(); 1233 1234private: 1235 ${cpprootmodelclassname}* fTarget; 1236}; 1237 1238""").substitute(naming.todict())) 1239 1240# class interface for concrete class of single listener 1241 1242 1243 # If bulk enveloping is selected then also output a listener and an interface 1244 # which can deal with call-backs. 1245 1246 if supportbulkcontainer: 1247 cpphfile.write( 1248 string.Template(""" 1249/*! Concrete sub-classes of this class are able to respond to each 1250 ${cpprootmodelclassname}* instance as 1251 it is parsed from the bulk container. When the stream is 1252 finished, the Complete() method is invoked. 1253 1254 Note that the item object will be deleted after the Handle method 1255 is invoked. The Handle method need not take responsibility 1256 for deleting the item itself. 1257*/ 1258class ${cppitemlistenerclassname} { 1259public: 1260 virtual bool Handle(${cpprootmodelclassname}* item) = 0; 1261 virtual void Complete() = 0; 1262}; 1263""").substitute(naming.todict())) 1264 1265 cpphfile.write( 1266 string.Template(""" 1267 1268 1269/*! Use this listener, together with an instance of a concrete 1270 subclass of ${cppitemlistenerclassname} 1271 in order to parse the JSON data in a specific "bulk 1272 container" format. Each time that an instance of 1273 ${cpprootmodelclassname} 1274 is parsed, the instance item listener will be invoked. 1275*/ 1276class ${cppbulkcontainermainlistenerclassname} 1277 : public ${cppsupermainlistenerclassname} { 1278friend class ${cppsuperstackedlistenerclassname}; 1279public: 1280 ${cppbulkcontainermainlistenerclassname}( 1281 ${cppitemlistenerclassname}* itemListener); 1282 ~${cppbulkcontainermainlistenerclassname}(); 1283 1284 bool Handle(const BJsonEvent& event); 1285 1286private: 1287 ${cppitemlistenerclassname}* fItemListener; 1288}; 1289""").substitute(naming.todict())) 1290 1291 cpphfile.write('\n#endif // %s' % guarddefname) 1292 1293 with open(cppimplementationfilename, 'w') as cppifile: 1294 istate = CppParserImplementationState(cppifile, naming) 1295 jscom.writetopcomment(cppifile, os.path.split(inputfile)[1], 'Listener') 1296 cppifile.write('#include "%s"\n' % cppheaderleafname) 1297 cppifile.write('#include <ObjectList.h>\n\n') 1298 cppifile.write('#include <stdio.h>\n\n') 1299 1300 cppifile.write('// #pragma mark - private interfaces for the stacked listeners\n\n') 1301 1302 writerootstackedlistenerinterface(istate) 1303 writegeneralstackedlistenerinterface(istate) 1304 writestringliststackedlistenerinterface(istate) 1305 writestackedlistenerinterface(istate, schema) 1306 1307 if supportbulkcontainer: 1308 writebulkcontainerstackedlistenerinterface(istate, schema) 1309 1310 cppifile.write('// #pragma mark - implementations for the stacked listeners\n\n') 1311 1312 writerootstackedlistenerimplementation(istate) 1313 writegeneralstackedlistenerimplementation(istate) 1314 writestringliststackedlistenerimplementation(istate) 1315 writestackedlistenerimplementation(istate, schema) 1316 1317 if supportbulkcontainer: 1318 writebulkcontainerstackedlistenerimplementation(istate, schema) 1319 1320 cppifile.write('// #pragma mark - implementations for the main listeners\n\n') 1321 1322 writemainlistenerimplementation(istate, schema, supportbulkcontainer) 1323 1324 1325def main(): 1326 parser = argparse.ArgumentParser(description='Convert JSON schema to Haiku C++ Parsers') 1327 parser.add_argument('-i', '--inputfile', required=True, help='The input filename containing the JSON schema') 1328 parser.add_argument('--outputdirectory', help='The output directory where the C++ files should be written') 1329 parser.add_argument('--supportbulkcontainer', help='Produce a parser that deals with a bulk envelope of items', 1330 action='store_true') 1331 1332 args = parser.parse_args() 1333 1334 outputdirectory = args.outputdirectory 1335 1336 if not outputdirectory: 1337 outputdirectory = '.' 1338 1339 with open(args.inputfile) as inputfile: 1340 schema = json.load(inputfile) 1341 schematocppparser(args.inputfile, schema, outputdirectory, args.supportbulkcontainer or False) 1342 1343if __name__ == "__main__": 1344 main()