xref: /haiku/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py (revision 6011ce6c7495e4e707bd33b12a7e22d66c710aad)
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
9# some model objects that can be used to hold the data-structure
10# in the C++ environment.
11
12import json
13import argparse
14import os
15import hdsjsonschemacommon as jscom
16import string
17
18
19def hasanylistproperties(schema):
20    for propname, propmetadata in schema['properties'].items():
21        if propmetadata['type'] == jscom.JSON_TYPE_ARRAY:
22            return True
23    return False
24
25
26def writelistaccessors(outputfile, cppclassname, cppname, cppmembername, cppcontainertype):
27
28    dict = {
29        'cppclassname' : cppclassname,
30        'cppname': cppname,
31        'cppmembername': cppmembername,
32        'cppcontainertype': cppcontainertype
33    }
34
35    outputfile.write(
36        string.Template("""
37void
38${cppclassname}::AddTo${cppname}(${cppcontainertype}* value)
39{
40    if (${cppmembername} == NULL)
41        ${cppmembername} = new BObjectList<${cppcontainertype}>();
42    ${cppmembername}->AddItem(value);
43}
44
45
46void
47${cppclassname}::Set${cppname}(BObjectList<${cppcontainertype}>* value)
48{
49   ${cppmembername} = value;
50}
51
52
53int32
54${cppclassname}::Count${cppname}()
55{
56    if (${cppmembername} == NULL)
57        return 0;
58    return ${cppmembername}->CountItems();
59}
60
61
62${cppcontainertype}*
63${cppclassname}::${cppname}ItemAt(int32 index)
64{
65    return ${cppmembername}->ItemAt(index);
66}
67
68
69bool
70${cppclassname}::${cppname}IsNull()
71{
72    return ${cppmembername} == NULL;
73}
74""").substitute(dict))
75
76
77def writelistaccessorsheader(outputfile, cppname, cppcontainertype):
78    dict = {
79        'cppname': cppname,
80        'cppcontainertype': cppcontainertype
81    }
82
83    outputfile.write(
84        string.Template("""    void AddTo${cppname}(${cppcontainertype}* value);
85    void Set${cppname}(BObjectList<${cppcontainertype}>* value);
86    int32 Count${cppname}();
87    ${cppcontainertype}* ${cppname}ItemAt(int32 index);
88    bool ${cppname}IsNull();
89""").substitute(dict))
90
91
92def writetakeownershipaccessors(outputfile, cppclassname, cppname, cppmembername, cpptype):
93
94    dict = {
95        'cppclassname': cppclassname,
96        'cppname': cppname,
97        'cppmembername': cppmembername,
98        'cpptype': cpptype
99    }
100
101    outputfile.write(
102        string.Template("""
103${cpptype}*
104${cppclassname}::${cppname}()
105{
106    return ${cppmembername};
107}
108
109
110void
111${cppclassname}::Set${cppname}(${cpptype}* value)
112{
113    ${cppmembername} = value;
114}
115
116
117void
118${cppclassname}::Set${cppname}Null()
119{
120    if (!${cppname}IsNull()) {
121        delete ${cppmembername};
122        ${cppmembername} = NULL;
123    }
124}
125
126
127bool
128${cppclassname}::${cppname}IsNull()
129{
130    return ${cppmembername} == NULL;
131}
132""").substitute(dict))
133
134
135def writetakeownershipaccessorsheader(outputfile, cppname, cpptype):
136    outputfile.write('    %s* %s();\n' % (cpptype, cppname))
137    outputfile.write('    void Set%s(%s* value);\n' % (cppname, cpptype))
138    outputfile.write('    void Set%sNull();\n' % cppname)
139    outputfile.write('    bool %sIsNull();\n' % cppname)
140
141
142def writescalaraccessors(outputfile, cppclassname, cppname, cppmembername, cpptype):
143
144    dict = {
145        'cppclassname': cppclassname,
146        'cppname': cppname,
147        'cppmembername': cppmembername,
148        'cpptype': cpptype
149    }
150
151    outputfile.write(
152        string.Template("""
153${cpptype}
154${cppclassname}::${cppname}()
155{""").substitute(dict))
156
157    if cpptype == jscom.CPP_TYPE_BOOLEAN:
158        outputfile.write(string.Template("""
159    if (${cppname}IsNull())
160        return false;
161""").substitute(dict))
162
163    if cpptype == jscom.CPP_TYPE_INTEGER:
164        outputfile.write(string.Template("""
165    if (${cppname}IsNull())
166        return 0;
167""").substitute(dict))
168
169    if cpptype == jscom.CPP_TYPE_NUMBER:
170        outputfile.write(string.Template("""
171    if (${cppname}IsNull())
172        return 0.0;
173""").substitute(dict))
174
175    outputfile.write(string.Template("""
176    return *${cppmembername};
177}
178
179
180void
181${cppclassname}::Set${cppname}(${cpptype} value)
182{
183    if (${cppname}IsNull())
184        ${cppmembername} = new ${cpptype}[1];
185    ${cppmembername}[0] = value;
186}
187
188
189void
190${cppclassname}::Set${cppname}Null()
191{
192    if (!${cppname}IsNull()) {
193        delete ${cppmembername};
194        ${cppmembername} = NULL;
195    }
196}
197
198
199bool
200${cppclassname}::${cppname}IsNull()
201{
202    return ${cppmembername} == NULL;
203}
204""").substitute(dict))
205
206
207def writescalaraccessorsheader(outputfile, cppname, cpptype):
208    outputfile.write(
209        string.Template("""
210    ${cpptype} ${cppname}();
211    void Set${cppname}(${cpptype} value);
212    void Set${cppname}Null();
213    bool ${cppname}IsNull();
214""").substitute({'cppname': cppname, 'cpptype': cpptype}))
215
216
217def writeaccessors(outputfile, cppclassname, propname, propmetadata):
218    type = propmetadata['type']
219
220    if type == jscom.JSON_TYPE_ARRAY:
221        writelistaccessors(outputfile,
222                           cppclassname,
223                           jscom.propnametocppname(propname),
224                           jscom.propnametocppmembername(propname),
225                           jscom.propmetadatatocpptypename(propmetadata['items']))
226    elif jscom.propmetadatatypeisscalar(propmetadata):
227        writescalaraccessors(outputfile,
228                             cppclassname,
229                             jscom.propnametocppname(propname),
230                             jscom.propnametocppmembername(propname),
231                             jscom.propmetadatatocpptypename(propmetadata))
232    else:
233        writetakeownershipaccessors(outputfile,
234                                    cppclassname,
235                                    jscom.propnametocppname(propname),
236                                    jscom.propnametocppmembername(propname),
237                                    jscom.propmetadatatocpptypename(propmetadata))
238
239
240def writeaccessorsheader(outputfile, propname, propmetadata):
241    type = propmetadata['type']
242
243    if type == jscom.JSON_TYPE_ARRAY:
244        writelistaccessorsheader(outputfile,
245                                 jscom.propnametocppname(propname),
246                                 jscom.propmetadatatocpptypename(propmetadata['items']))
247    elif jscom.propmetadatatypeisscalar(propmetadata):
248        writescalaraccessorsheader(outputfile,
249                                   jscom.propnametocppname(propname),
250                                   jscom.propmetadatatocpptypename(propmetadata))
251    else:
252        writetakeownershipaccessorsheader(outputfile,
253                                          jscom.propnametocppname(propname),
254                                          jscom.propmetadatatocpptypename(propmetadata))
255
256
257def writedestructorlogicforlist(outputfile, propname, propmetadata):
258    dict = {
259        'cppmembername': jscom.propnametocppmembername(propname),
260        'cpptype': jscom.javatypetocppname(propmetadata['items']['javaType'])
261    }
262
263    outputfile.write(
264        string.Template("""        int32 count = ${cppmembername}->CountItems();
265        for (i = 0; i < count; i++)
266            delete ${cppmembername}->ItemAt(i);
267""").substitute(dict))
268
269
270def writedestructor(outputfile, cppname, schema):
271    outputfile.write('\n\n%s::~%s()\n{\n' % (cppname, cppname))
272
273    if hasanylistproperties(schema):
274        outputfile.write('    int32 i;\n\n')
275
276    for propname, propmetadata in schema['properties'].items():
277        propmembername = jscom.propnametocppmembername(propname)
278
279        outputfile.write('    if (%s != NULL) {\n' % propmembername)
280
281        if propmetadata['type'] == jscom.JSON_TYPE_ARRAY:
282            writedestructorlogicforlist(outputfile, propname, propmetadata)
283
284        outputfile.write((
285            '        delete %s;\n'
286        ) % propmembername)
287
288        outputfile.write('    }\n\n')
289
290    outputfile.write('}\n')
291
292
293def writeconstructor(outputfile, cppname, schema):
294    outputfile.write('\n\n%s::%s()\n{\n' % (cppname, cppname))
295
296    for propname, propmetadata in schema['properties'].items():
297        outputfile.write('    %s = NULL;\n' % jscom.propnametocppmembername(propname))
298
299    outputfile.write('}\n')
300
301
302def writeheaderincludes(outputfile, properties):
303    for propname, propmetadata in properties.items():
304        jsontype = propmetadata['type']
305        javatype = None
306
307        if jsontype == jscom.JSON_TYPE_OBJECT:
308            javatype = propmetadata['javaType']
309
310        if jsontype == jscom.JSON_TYPE_ARRAY:
311            javatype = propmetadata['items']['javaType']
312
313        if javatype is not None:
314            outputfile.write('#include "%s.h"\n' % jscom.javatypetocppname(javatype))
315
316
317def schematocppmodels(inputfile, schema, outputdirectory):
318    type = schema['type']
319    if type != jscom.JSON_TYPE_OBJECT:
320        raise Exception('expecting object, but was [' + type + ']')
321
322    javatype = schema['javaType']
323
324    if not javatype or 0 == len(javatype):
325        raise Exception('missing "javaType" field')
326
327    cppclassname = jscom.javatypetocppname(javatype)
328    cpphfilename = os.path.join(outputdirectory, cppclassname + '.h')
329    cppifilename = os.path.join(outputdirectory, cppclassname + '.cpp')
330
331    with open(cpphfilename, 'w') as cpphfile:
332
333        jscom.writetopcomment(cpphfile, os.path.split(inputfile)[1], 'Model')
334        guarddefname = 'GEN_JSON_SCHEMA_MODEL__%s_H' % (cppclassname.upper())
335
336        cpphfile.write(string.Template("""
337#ifndef ${guarddefname}
338#define ${guarddefname}
339
340#include <ObjectList.h>
341#include <String.h>
342
343""").substitute({'guarddefname': guarddefname}))
344
345        writeheaderincludes(cpphfile, schema['properties'])
346
347        cpphfile.write(string.Template("""
348class ${cppclassname} {
349public:
350    ${cppclassname}();
351    virtual ~${cppclassname}();
352
353
354""").substitute({'cppclassname': cppclassname}))
355
356        for propname, propmetadata in schema['properties'].items():
357            writeaccessorsheader(cpphfile, propname, propmetadata)
358            cpphfile.write('\n')
359
360        # Now add the instance variables for the object as well.
361
362        cpphfile.write('private:\n')
363
364        for propname, propmetadata in schema['properties'].items():
365            cpphfile.write('    %s* %s;\n' % (
366                jscom.propmetadatatocpptypename(propmetadata),
367                jscom.propnametocppmembername(propname)))
368
369        cpphfile.write((
370            '};\n\n'
371            '#endif // %s'
372        ) % guarddefname)
373
374    with open(cppifilename, 'w') as cppifile:
375
376        jscom.writetopcomment(cppifile, os.path.split(inputfile)[1], 'Model')
377
378        cppifile.write('#include "%s.h"\n' % cppclassname)
379
380        writeconstructor(cppifile, cppclassname, schema)
381        writedestructor(cppifile, cppclassname, schema)
382
383        for propname, propmetadata in schema['properties'].items():
384            writeaccessors(cppifile, cppclassname, propname, propmetadata)
385            cppifile.write('\n')
386
387    # Now write out any subordinate structures.
388
389    for propname, propmetadata in schema['properties'].items():
390        jsontype = propmetadata['type']
391
392        if jsontype == jscom.JSON_TYPE_ARRAY:
393            arraySchema = propmetadata['items']
394            if arraySchema['type'] == jscom.JSON_TYPE_OBJECT:
395                schematocppmodels(inputfile, arraySchema, outputdirectory)
396
397        if jsontype == jscom.JSON_TYPE_OBJECT:
398            schematocppmodels(inputfile, propmetadata, outputdirectory)
399
400
401def main():
402    parser = argparse.ArgumentParser(description='Convert JSON schema to Haiku C++ Models')
403    parser.add_argument('-i', '--inputfile', required=True, help='The input filename containing the JSON schema')
404    parser.add_argument('--outputdirectory', help='The output directory where the C++ files should be written')
405
406    args = parser.parse_args()
407
408    outputdirectory = args.outputdirectory
409
410    if not outputdirectory:
411        outputdirectory = '.'
412
413    with open(args.inputfile) as inputfile:
414        schema = json.load(inputfile)
415        schematocppmodels(args.inputfile, schema, outputdirectory)
416
417if __name__ == "__main__":
418    main()
419
420