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