xref: /haiku/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py (revision d1f885b435e9892ac028f4be2b80536b9dd37413)
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
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'] == '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 List<${cppcontainertype}*, true>();
42    ${cppmembername}->Add(value);
43}
44
45
46void
47${cppclassname}::Set${cppname}(List<${cppcontainertype}*, true>* 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}(List<${cppcontainertype}*, true>* 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{
156    return *${cppmembername};
157}
158
159
160void
161${cppclassname}::Set${cppname}(${cpptype} value)
162{
163    if (${cppname}IsNull())
164        ${cppmembername} = new ${cpptype}[1];
165    ${cppmembername}[0] = value;
166}
167
168
169void
170${cppclassname}::Set${cppname}Null()
171{
172    if (!${cppname}IsNull()) {
173        delete ${cppmembername};
174        ${cppmembername} = NULL;
175    }
176}
177
178
179bool
180${cppclassname}::${cppname}IsNull()
181{
182    return ${cppmembername} == NULL;
183}
184""").substitute(dict))
185
186
187def writescalaraccessorsheader(outputfile, cppname, cpptype):
188    outputfile.write(
189        string.Template("""
190    ${cpptype} ${cppname}();
191    void Set${cppname}(${cpptype} value);
192    void Set${cppname}Null();
193    bool ${cppname}IsNull();
194""").substitute({'cppname': cppname, 'cpptype': cpptype}))
195
196
197def writeaccessors(outputfile, cppclassname, propname, propmetadata):
198    type = propmetadata['type']
199
200    if type == 'array':
201        writelistaccessors(outputfile,
202                           cppclassname,
203                           jscom.propnametocppname(propname),
204                           jscom.propnametocppmembername(propname),
205                           jscom.javatypetocppname(propmetadata['items']['javaType']))
206    elif jscom.propmetadatatypeisscalar(propmetadata):
207        writescalaraccessors(outputfile,
208                             cppclassname,
209                             jscom.propnametocppname(propname),
210                             jscom.propnametocppmembername(propname),
211                             jscom.propmetadatatocpptypename(propmetadata))
212    else:
213        writetakeownershipaccessors(outputfile,
214                                    cppclassname,
215                                    jscom.propnametocppname(propname),
216                                    jscom.propnametocppmembername(propname),
217                                    jscom.propmetadatatocpptypename(propmetadata))
218
219
220def writeaccessorsheader(outputfile, propname, propmetadata):
221    type = propmetadata['type']
222
223    if type == 'array':
224        writelistaccessorsheader(outputfile,
225                                 jscom.propnametocppname(propname),
226                                 jscom.javatypetocppname(propmetadata['items']['javaType']))
227    elif jscom.propmetadatatypeisscalar(propmetadata):
228        writescalaraccessorsheader(outputfile,
229                                   jscom.propnametocppname(propname),
230                                   jscom.propmetadatatocpptypename(propmetadata))
231    else:
232        writetakeownershipaccessorsheader(outputfile,
233                                          jscom.propnametocppname(propname),
234                                          jscom.propmetadatatocpptypename(propmetadata))
235
236
237def writedestructorlogicforlist(outputfile, propname, propmetadata):
238    dict = {
239        'cppmembername': jscom.propnametocppmembername(propname),
240        'cpptype': jscom.javatypetocppname(propmetadata['items']['javaType'])
241    }
242
243    outputfile.write(
244        string.Template("""        int32 count = ${cppmembername}->CountItems();
245        for (i = 0; i < count; i++)
246            delete ${cppmembername}->ItemAt(i);
247""").substitute(dict))
248
249
250def writedestructor(outputfile, cppname, schema):
251    outputfile.write('\n\n%s::~%s()\n{\n' % (cppname, cppname))
252
253    if hasanylistproperties(schema):
254        outputfile.write('    int32 i;\n\n')
255
256    for propname, propmetadata in schema['properties'].items():
257        propmembername = jscom.propnametocppmembername(propname)
258
259        outputfile.write('    if (%s != NULL) {\n' % propmembername)
260
261        if propmetadata['type'] == 'array':
262            writedestructorlogicforlist(outputfile, propname, propmetadata)
263
264        outputfile.write((
265            '        delete %s;\n'
266        ) % propmembername)
267
268        outputfile.write('    }\n\n')
269
270    outputfile.write('}\n')
271
272
273def writeconstructor(outputfile, cppname, schema):
274    outputfile.write('\n\n%s::%s()\n{\n' % (cppname, cppname))
275
276    for propname, propmetadata in schema['properties'].items():
277        outputfile.write('    %s = NULL;\n' % jscom.propnametocppmembername(propname))
278
279    outputfile.write('}\n')
280
281
282def writeheaderincludes(outputfile, properties):
283    for propname, propmetadata in properties.items():
284        jsontype = propmetadata['type']
285        javatype = None
286
287        if jsontype == 'object':
288            javatype = propmetadata['javaType']
289
290        if jsontype == 'array':
291            javatype = propmetadata['items']['javaType']
292
293        if javatype is not None:
294            outputfile.write('#include "%s.h"\n' % jscom.javatypetocppname(javatype))
295
296
297def schematocppmodels(inputfile, schema, outputdirectory):
298    if schema['type'] != 'object':
299        raise Exception('expecting object')
300
301    javatype = schema['javaType']
302
303    if not javatype or 0 == len(javatype):
304        raise Exception('missing "javaType" field')
305
306    cppclassname = jscom.javatypetocppname(javatype)
307    cpphfilename = os.path.join(outputdirectory, cppclassname + '.h')
308    cppifilename = os.path.join(outputdirectory, cppclassname + '.cpp')
309
310    with open(cpphfilename, 'w') as cpphfile:
311
312        jscom.writetopcomment(cpphfile, os.path.split(inputfile)[1], 'Model')
313        guarddefname = 'GEN_JSON_SCHEMA_MODEL__%s_H' % (cppclassname.upper())
314
315        cpphfile.write(string.Template("""
316#ifndef ${guarddefname}
317#define ${guarddefname}
318
319#include "List.h"
320#include "String.h"
321
322""").substitute({'guarddefname': guarddefname}))
323
324        writeheaderincludes(cpphfile, schema['properties'])
325
326        cpphfile.write(string.Template("""
327class ${cppclassname} {
328public:
329    ${cppclassname}();
330    virtual ~${cppclassname}();
331
332
333""").substitute({'cppclassname': cppclassname}))
334
335        for propname, propmetadata in schema['properties'].items():
336            writeaccessorsheader(cpphfile, propname, propmetadata)
337            cpphfile.write('\n')
338
339        # Now add the instance variables for the object as well.
340
341        cpphfile.write('private:\n')
342
343        for propname, propmetadata in schema['properties'].items():
344            cpphfile.write('    %s* %s;\n' % (
345                jscom.propmetadatatocpptypename(propmetadata),
346                jscom.propnametocppmembername(propname)))
347
348        cpphfile.write((
349            '};\n\n'
350            '#endif // %s'
351        ) % guarddefname)
352
353    with open(cppifilename, 'w') as cppifile:
354
355        jscom.writetopcomment(cppifile, os.path.split(inputfile)[1], 'Model')
356
357        cppifile.write('#include "%s.h"\n' % cppclassname)
358
359        writeconstructor(cppifile, cppclassname, schema)
360        writedestructor(cppifile, cppclassname, schema)
361
362        for propname, propmetadata in schema['properties'].items():
363            writeaccessors(cppifile, cppclassname, propname, propmetadata)
364            cppifile.write('\n')
365
366    # Now write out any subordinate structures.
367
368    for propname, propmetadata in schema['properties'].items():
369        jsontype = propmetadata['type']
370
371        if jsontype == 'array':
372            schematocppmodels(inputfile, propmetadata['items'], outputdirectory)
373
374        if jsontype == 'object':
375            schematocppmodels(inputfile, propmetadata, outputdirectory)
376
377
378def main():
379    parser = argparse.ArgumentParser(description='Convert JSON schema to Haiku C++ Models')
380    parser.add_argument('-i', '--inputfile', required=True, help='The input filename containing the JSON schema')
381    parser.add_argument('--outputdirectory', help='The output directory where the C++ files should be written')
382
383    args = parser.parse_args()
384
385    outputdirectory = args.outputdirectory
386
387    if not outputdirectory:
388        outputdirectory = '.'
389
390    with open(args.inputfile) as inputfile:
391        schema = json.load(inputfile)
392        schematocppmodels(args.inputfile, schema, outputdirectory)
393
394if __name__ == "__main__":
395    main()
396
397