xref: /haiku/src/apps/haikudepot/build/scripts/jsonschema2cppparser.py (revision 0f9ffb37c166a9d9257044c8937f6450f4257b75)
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()