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