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{""").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 == 'array':
221        writelistaccessors(outputfile,
222                           cppclassname,
223                           jscom.propnametocppname(propname),
224                           jscom.propnametocppmembername(propname),
225                           jscom.javatypetocppname(propmetadata['items']['javaType']))
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 == 'array':
244        writelistaccessorsheader(outputfile,
245                                 jscom.propnametocppname(propname),
246                                 jscom.javatypetocppname(propmetadata['items']['javaType']))
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'] == '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 == 'object':
308            javatype = propmetadata['javaType']
309
310        if jsontype == '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    if schema['type'] != 'object':
319        raise Exception('expecting object')
320
321    javatype = schema['javaType']
322
323    if not javatype or 0 == len(javatype):
324        raise Exception('missing "javaType" field')
325
326    cppclassname = jscom.javatypetocppname(javatype)
327    cpphfilename = os.path.join(outputdirectory, cppclassname + '.h')
328    cppifilename = os.path.join(outputdirectory, cppclassname + '.cpp')
329
330    with open(cpphfilename, 'w') as cpphfile:
331
332        jscom.writetopcomment(cpphfile, os.path.split(inputfile)[1], 'Model')
333        guarddefname = 'GEN_JSON_SCHEMA_MODEL__%s_H' % (cppclassname.upper())
334
335        cpphfile.write(string.Template("""
336#ifndef ${guarddefname}
337#define ${guarddefname}
338
339#include "List.h"
340#include "String.h"
341
342""").substitute({'guarddefname': guarddefname}))
343
344        writeheaderincludes(cpphfile, schema['properties'])
345
346        cpphfile.write(string.Template("""
347class ${cppclassname} {
348public:
349    ${cppclassname}();
350    virtual ~${cppclassname}();
351
352
353""").substitute({'cppclassname': cppclassname}))
354
355        for propname, propmetadata in schema['properties'].items():
356            writeaccessorsheader(cpphfile, propname, propmetadata)
357            cpphfile.write('\n')
358
359        # Now add the instance variables for the object as well.
360
361        cpphfile.write('private:\n')
362
363        for propname, propmetadata in schema['properties'].items():
364            cpphfile.write('    %s* %s;\n' % (
365                jscom.propmetadatatocpptypename(propmetadata),
366                jscom.propnametocppmembername(propname)))
367
368        cpphfile.write((
369            '};\n\n'
370            '#endif // %s'
371        ) % guarddefname)
372
373    with open(cppifilename, 'w') as cppifile:
374
375        jscom.writetopcomment(cppifile, os.path.split(inputfile)[1], 'Model')
376
377        cppifile.write('#include "%s.h"\n' % cppclassname)
378
379        writeconstructor(cppifile, cppclassname, schema)
380        writedestructor(cppifile, cppclassname, schema)
381
382        for propname, propmetadata in schema['properties'].items():
383            writeaccessors(cppifile, cppclassname, propname, propmetadata)
384            cppifile.write('\n')
385
386    # Now write out any subordinate structures.
387
388    for propname, propmetadata in schema['properties'].items():
389        jsontype = propmetadata['type']
390
391        if jsontype == 'array':
392            schematocppmodels(inputfile, propmetadata['items'], outputdirectory)
393
394        if jsontype == 'object':
395            schematocppmodels(inputfile, propmetadata, outputdirectory)
396
397
398def main():
399    parser = argparse.ArgumentParser(description='Convert JSON schema to Haiku C++ Models')
400    parser.add_argument('-i', '--inputfile', required=True, help='The input filename containing the JSON schema')
401    parser.add_argument('--outputdirectory', help='The output directory where the C++ files should be written')
402
403    args = parser.parse_args()
404
405    outputdirectory = args.outputdirectory
406
407    if not outputdirectory:
408        outputdirectory = '.'
409
410    with open(args.inputfile) as inputfile:
411        schema = json.load(inputfile)
412        schematocppmodels(args.inputfile, schema, outputdirectory)
413
414if __name__ == "__main__":
415    main()
416
417