0
|
1 #!/usr/bin/env python3
|
|
2
|
18
|
3 # SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium
|
0
|
4 # SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
|
6 # Java plugin for Orthanc
|
18
|
7 # Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium
|
0
|
8 #
|
|
9 # This program is free software: you can redistribute it and/or
|
|
10 # modify it under the terms of the GNU General Public License as
|
|
11 # published by the Free Software Foundation, either version 3 of the
|
|
12 # License, or (at your option) any later version.
|
|
13 #
|
|
14 # This program is distributed in the hope that it will be useful, but
|
|
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
17 # General Public License for more details.
|
|
18 #
|
|
19 # You should have received a copy of the GNU General Public License
|
|
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
|
22
|
|
23 import argparse
|
|
24 import json
|
|
25 import os
|
|
26 import pystache
|
|
27
|
|
28
|
|
29 SOURCE = os.path.abspath(os.path.dirname(__file__))
|
|
30
|
|
31 parser = argparse.ArgumentParser(description = 'Generate Java wrapper from code model.')
|
|
32 parser.add_argument('--source',
|
|
33 default = os.path.join(SOURCE, 'CodeModel.json'),
|
|
34 help = 'location of the JSON code model')
|
|
35
|
|
36 args = parser.parse_args()
|
|
37
|
|
38
|
|
39
|
|
40 TARGET = os.path.join(SOURCE, '..', 'JavaSDK', 'be', 'uclouvain', 'orthanc')
|
|
41
|
|
42 with open(args.source, 'r') as f:
|
|
43 model = json.loads(f.read())
|
|
44
|
|
45 with open(os.path.join(SOURCE, 'ClassDocumentation.json'), 'r') as f:
|
|
46 classDocumentation = json.loads(f.read())
|
|
47
|
|
48
|
|
49 renderer = pystache.Renderer(
|
|
50 escape = lambda u: u, # No escaping
|
|
51 )
|
|
52
|
|
53
|
|
54 def ToUpperCase(name):
|
|
55 s = ''
|
|
56 for i in range(len(name)):
|
|
57 if name[i].isupper():
|
|
58 if len(s) == 0:
|
|
59 s += name[i]
|
|
60 elif name[i - 1].islower():
|
|
61 s += '_' + name[i]
|
|
62 elif (i + 1 < len(name) and
|
|
63 name[i - 1].islower() and
|
|
64 name[i + 1].isupper()):
|
|
65 s += '_' + name[i]
|
|
66 else:
|
|
67 s += name[i]
|
|
68 else:
|
|
69 s += name[i].upper()
|
|
70 return s
|
|
71
|
|
72
|
|
73 def RemoveOrthancPluginPrefix(s, isCamelCase):
|
|
74 PREFIX = 'OrthancPlugin'
|
|
75 if s.startswith(PREFIX):
|
|
76 t = s[len(PREFIX):]
|
|
77 if isCamelCase:
|
|
78 t = t[0].lower() + t[1:]
|
|
79 return t
|
|
80 else:
|
|
81 raise Exception('Incorrect prefix: %s' % s)
|
|
82
|
|
83
|
|
84 def ConvertReturnType(f):
|
|
85 result = None
|
|
86
|
|
87 if f['return_sdk_type'] == 'void':
|
|
88 result = {
|
|
89 'c_type' : 'void',
|
|
90 'is_void' : True,
|
|
91 'java_signature' : 'V',
|
|
92 'java_type' : 'void',
|
|
93 }
|
|
94 elif f['return_sdk_type'] in [ 'int', 'int32_t', 'uint32_t' ]:
|
|
95 result = {
|
|
96 'c_type' : 'jint',
|
|
97 'default_value' : '0',
|
|
98 'is_number' : True,
|
|
99 'java_signature' : 'I',
|
|
100 'java_type' : 'int',
|
|
101 }
|
|
102 elif f['return_sdk_type'] in [ 'int64_t' ]:
|
|
103 result = {
|
|
104 'c_type' : 'jlong',
|
|
105 'default_value' : '0',
|
|
106 'is_number' : True,
|
|
107 'java_signature' : 'J',
|
|
108 'java_type' : 'long',
|
|
109 }
|
|
110 elif f['return_sdk_type'] == 'OrthancPluginMemoryBuffer *':
|
|
111 result = {
|
|
112 'c_type' : 'jbyteArray',
|
|
113 'default_value' : 'NULL',
|
|
114 'is_bytes' : True,
|
|
115 'java_signature' : '[B',
|
|
116 'java_type' : 'byte[]',
|
|
117 }
|
|
118 elif f['return_sdk_type'] == 'enumeration':
|
|
119 if f['return_sdk_enumeration'] == 'OrthancPluginErrorCode':
|
|
120 result = {
|
|
121 'c_type' : 'void',
|
|
122 'is_exception' : True,
|
|
123 'java_signature' : 'V',
|
|
124 'java_type' : 'void',
|
|
125 }
|
|
126 else:
|
|
127 result = {
|
|
128 'c_type' : 'jint',
|
|
129 'default_value' : '0',
|
|
130 'is_enumeration' : True,
|
|
131 'java_wrapper_type' : RemoveOrthancPluginPrefix(f['return_sdk_enumeration'], False),
|
|
132 'java_signature' : 'I',
|
|
133 'java_type' : 'int',
|
|
134 }
|
|
135 elif f['return_sdk_type'] == 'object':
|
|
136 result = {
|
|
137 'c_type' : 'jlong',
|
|
138 'class_name' : f['return_sdk_class'],
|
|
139 'default_value' : '0',
|
|
140 'is_object' : True,
|
|
141 'java_wrapper_type' : RemoveOrthancPluginPrefix(f['return_sdk_class'], False),
|
|
142 'java_signature' : 'J',
|
|
143 'java_type' : 'long',
|
|
144 }
|
|
145 elif f['return_sdk_type'] == 'char *':
|
|
146 result = {
|
|
147 'c_type' : 'jstring',
|
|
148 'default_value' : 'NULL',
|
|
149 'is_dynamic_string' : True,
|
|
150 'java_signature' : 'Ljava/lang/String;',
|
|
151 'java_type' : 'String',
|
|
152 }
|
|
153 elif f['return_sdk_type'] == 'const char *':
|
|
154 result = {
|
|
155 'c_type' : 'jstring',
|
|
156 'default_value' : 'NULL',
|
|
157 'is_static_string' : True,
|
|
158 'java_signature' : 'Ljava/lang/String;',
|
|
159 'java_type' : 'String',
|
|
160 }
|
|
161 else:
|
|
162 raise Exception('Unsupported return type: %s' % json.dumps(f, indent=4))
|
|
163
|
|
164 if not 'java_wrapper_type' in result:
|
|
165 result['java_wrapper_type'] = result['java_type']
|
|
166
|
|
167 return result
|
|
168
|
|
169 def ConvertArgument(arg):
|
|
170 result = None
|
|
171
|
|
172 if arg['sdk_type'] in [ 'int', 'int32_t', 'uint32_t' ]:
|
|
173 result = {
|
|
174 'c_type' : 'jint',
|
|
175 'java_signature' : 'I',
|
|
176 'java_type' : 'int',
|
|
177 }
|
|
178 elif arg['sdk_type'] == 'uint8_t':
|
|
179 result = {
|
|
180 'c_type' : 'jbyte',
|
|
181 'java_signature' : 'B',
|
|
182 'java_type' : 'byte',
|
|
183 }
|
|
184 elif arg['sdk_type'] == 'uint16_t':
|
|
185 result = {
|
|
186 'c_type' : 'jshort',
|
|
187 'java_signature' : 'S',
|
|
188 'java_type' : 'short',
|
|
189 }
|
|
190 elif arg['sdk_type'] == 'uint64_t':
|
|
191 result = {
|
|
192 'c_type' : 'jlong',
|
|
193 'java_signature' : 'J',
|
|
194 'java_type' : 'long',
|
|
195 }
|
|
196 elif arg['sdk_type'] == 'float':
|
|
197 result = {
|
|
198 'c_type' : 'jfloat',
|
|
199 'java_signature' : 'F',
|
|
200 'java_type' : 'float',
|
|
201 }
|
|
202 elif arg['sdk_type'] == 'const char *':
|
|
203 result = {
|
|
204 'c_accessor' : 'c_%s.GetValue()' % arg['name'],
|
|
205 'c_type' : 'jstring',
|
|
206 'convert_string' : True,
|
|
207 'java_signature' : 'Ljava/lang/String;',
|
|
208 'java_type' : 'String',
|
|
209 }
|
|
210 elif arg['sdk_type'] == 'const_void_pointer_with_size':
|
|
211 result = {
|
|
212 'c_accessor' : 'c_%s.GetData(), c_%s.GetSize()' % (arg['name'], arg['name']),
|
|
213 'c_type' : 'jbyteArray',
|
|
214 'convert_bytes' : True,
|
|
215 'java_signature' : '[B',
|
|
216 'java_type' : 'byte[]',
|
|
217 }
|
|
218 elif arg['sdk_type'] == 'enumeration':
|
|
219 result = {
|
|
220 'c_accessor' : 'static_cast<%s>(%s)' % (arg['sdk_enumeration'], arg['name']),
|
|
221 'c_type' : 'jint',
|
|
222 'java_wrapper_accessor' : '%s.getValue()' % arg['sdk_name'],
|
|
223 'java_wrapper_type' : RemoveOrthancPluginPrefix(arg['sdk_enumeration'], False),
|
|
224 'java_signature' : 'I',
|
|
225 'java_type' : 'int',
|
|
226 }
|
|
227 elif arg['sdk_type'] == 'const void *':
|
|
228 result = {
|
|
229 'c_accessor' : 'c_%s.GetData()' % arg['name'],
|
|
230 'c_type' : 'jbyteArray',
|
|
231 'convert_bytes' : True,
|
|
232 'java_signature' : '[B',
|
|
233 'java_type' : 'byte[]',
|
|
234 }
|
|
235 elif arg['sdk_type'] in [ 'object', 'const_object' ]:
|
|
236 result = {
|
|
237 'c_accessor' : 'reinterpret_cast<%s*>(static_cast<intptr_t>(%s))' % (arg['sdk_class'], arg['name']),
|
|
238 'c_type' : 'jlong',
|
|
239 'java_signature' : 'J',
|
|
240 'java_type' : 'long',
|
|
241 'java_wrapper_accessor' : '%s.getSelf()' % arg['sdk_name'],
|
|
242 'java_wrapper_type' : RemoveOrthancPluginPrefix(arg['sdk_class'], False),
|
|
243 }
|
|
244 else:
|
|
245 raise Exception('Unsupported argument type: %s' % json.dumps(arg, indent=4))
|
|
246
|
|
247 result['name'] = arg['name']
|
|
248 result['sdk_name'] = arg['sdk_name']
|
|
249
|
|
250 if not 'java_wrapper_type' in result:
|
|
251 result['java_wrapper_type'] = result['java_type']
|
|
252
|
|
253 if not 'java_wrapper_accessor' in result:
|
|
254 result['java_wrapper_accessor'] = arg['sdk_name']
|
|
255
|
|
256 if not 'c_accessor' in result:
|
|
257 result['c_accessor'] = arg['name']
|
|
258
|
|
259 return result
|
|
260
|
|
261
|
|
262 def FixLinesWidth(source):
|
|
263 target = []
|
|
264
|
|
265 for line in source:
|
|
266 for word in line.split(' '):
|
|
267 if len(target) == 0:
|
|
268 target.append(word)
|
|
269 elif len(target[-1]) == 0:
|
|
270 target[-1] = word
|
|
271 elif len(target[-1] + ' ' + word) <= 80:
|
|
272 target[-1] = target[-1] + ' ' + word
|
|
273 else:
|
|
274 target.append(word)
|
|
275 target.append('')
|
|
276
|
|
277 while len(target) > 0:
|
|
278 if target[-1] == '':
|
|
279 target = target[:-1]
|
|
280 else:
|
|
281 break
|
|
282
|
|
283 return target
|
|
284
|
|
285
|
|
286 def EncodeFunctionDocumentation(f):
|
|
287 documentation = f['documentation']
|
|
288
|
|
289 paragraphs = [ ]
|
|
290 if 'summary' in documentation:
|
|
291 paragraphs.append(documentation['summary'])
|
|
292 paragraphs.append('')
|
|
293
|
|
294 if 'description' in documentation:
|
|
295 for line in documentation['description']:
|
|
296 paragraphs.append(line)
|
|
297 paragraphs.append('')
|
|
298
|
|
299 if 'args' in documentation:
|
|
300 for arg in f['args']:
|
|
301 name = arg['sdk_name']
|
|
302 if name in documentation['args']:
|
|
303 doc = documentation['args'][name]
|
|
304 paragraphs.append('@param %s %s' % (name, doc))
|
|
305
|
|
306 if 'return' in documentation:
|
|
307 if (f['return_sdk_type'] == 'enumeration' and
|
|
308 f['return_sdk_enumeration'] == 'OrthancPluginErrorCode'):
|
|
309 pass
|
|
310 elif f['return_sdk_type'] == 'object':
|
|
311 paragraphs.append('@return The newly constructed object.')
|
|
312 elif f['return_sdk_type'] in [ 'char *', 'const char *' ]:
|
|
313 paragraphs.append('@return The resulting string.')
|
|
314 elif f['return_sdk_type'] == 'OrthancPluginMemoryBuffer *':
|
|
315 paragraphs.append('@return The resulting memory buffer.')
|
|
316 else:
|
|
317 paragraphs.append('@return ' + documentation['return'])
|
|
318
|
|
319 lines = FixLinesWidth(paragraphs)
|
|
320
|
|
321 return list(map(lambda x: { 'line' : x }, lines))
|
|
322
|
|
323
|
|
324 def EncodeFunction(className, f):
|
|
325 args = []
|
|
326 for a in f['args']:
|
|
327 args.append(ConvertArgument(a))
|
|
328
|
|
329 if len(args) > 0:
|
|
330 args[-1]['last'] = True
|
|
331
|
|
332 returnType = ConvertReturnType(f)
|
|
333 signature = '(%s%s)%s' % ('J' if className != None else '',
|
|
334 ''.join(map(lambda x: x['java_signature'], args)),
|
|
335 returnType['java_signature'])
|
|
336
|
|
337 result = {
|
|
338 'args' : args,
|
|
339 'c_function' : f['c_function'],
|
|
340 'class_name' : className,
|
|
341 'has_args' : len(args) > 0,
|
|
342 'java_signature' : signature,
|
|
343 'return' : returnType,
|
|
344 'java_name' : RemoveOrthancPluginPrefix(f['c_function'], True),
|
|
345 }
|
|
346
|
|
347 if 'documentation' in f:
|
|
348 result['has_documentation'] = True
|
|
349 result['documentation'] = EncodeFunctionDocumentation(f)
|
|
350
|
|
351 if (returnType.get('is_number') == True or
|
|
352 returnType.get('is_bytes') == True or
|
|
353 returnType.get('is_dynamic_string') == True or
|
|
354 returnType.get('is_static_string') == True):
|
|
355 result['java_return_start'] = 'return '
|
|
356
|
|
357 elif returnType.get('is_enumeration') == True:
|
|
358 result['java_return_start'] = 'return %s.getInstance(' % returnType['java_wrapper_type']
|
|
359 result['java_return_end'] = ')'
|
|
360
|
|
361 elif returnType.get('is_object') == True:
|
|
362 result['java_return_start'] = 'return new %s(' % returnType['java_wrapper_type']
|
|
363 result['java_return_end'] = ')'
|
|
364
|
|
365 return result
|
|
366
|
|
367
|
|
368 nativeFunctions = []
|
|
369
|
|
370 for f in model['global_functions']:
|
|
371 nativeFunctions.append(EncodeFunction(None, f))
|
|
372
|
|
373
|
|
374 for c in model['classes']:
|
|
375 if 'destructor' in c:
|
|
376 nativeFunctions.append(EncodeFunction(c['name'], {
|
|
377 'args' : [],
|
|
378 'c_function' : c['destructor'],
|
|
379 'return_sdk_type' : 'void',
|
|
380 }))
|
|
381
|
|
382 for m in c['methods']:
|
|
383 nativeFunctions.append(EncodeFunction(c['name'], m))
|
|
384
|
|
385
|
|
386 with open(os.path.join(SOURCE, 'JavaNativeSDK.mustache'), 'r') as f:
|
|
387 template = f.read()
|
|
388
|
|
389 with open(os.path.join(TARGET, 'NativeSDK.java'), 'w') as g:
|
|
390 g.write(renderer.render(template, {
|
|
391 'functions' : nativeFunctions
|
|
392 }))
|
|
393
|
|
394
|
|
395 with open(os.path.join(SOURCE, 'JavaFunctions.mustache'), 'r') as f:
|
|
396 template = f.read()
|
|
397
|
|
398 with open(os.path.join(TARGET, 'Functions.java'), 'w') as g:
|
|
399 g.write(renderer.render(template, {
|
|
400 'functions' : filter(lambda x: (x['class_name'] == None and
|
|
401 x['return'].get('is_object') != True), nativeFunctions),
|
|
402 }))
|
|
403
|
|
404
|
|
405 with open(os.path.join(SOURCE, 'CppNativeSDK.mustache'), 'r') as f:
|
|
406 template = f.read()
|
|
407
|
|
408 with open(os.path.join(SOURCE, '..', 'Plugin', 'NativeSDK.cpp'), 'w') as g:
|
|
409 s = renderer.render(template, {
|
|
410 'functions' : nativeFunctions
|
|
411 })
|
|
412
|
|
413 s = s.splitlines()
|
|
414 s = filter(lambda l: not l.isspace() or len(l) == 0, s)
|
|
415
|
|
416 g.write('\n'.join(s))
|
|
417
|
|
418
|
|
419
|
|
420 for enum in model['enumerations']:
|
|
421 if not enum['name'].startswith('OrthancPlugin'):
|
|
422 raise Exception()
|
|
423
|
|
424 enum['short_name'] = enum['name'][len('OrthancPlugin'):]
|
|
425
|
|
426 for i in range(len(enum['values'])):
|
|
427 enum['values'][i]['key'] = ToUpperCase(enum['values'][i]['key'])
|
|
428
|
|
429 if 'documentation' in enum['values'][i]:
|
|
430 enum['values'][i]['has_documentation'] = True
|
|
431 enum['values'][i]['documentation'] = list(map(lambda x: { 'line' : x }, FixLinesWidth([ enum['values'][i]['documentation'] ])))
|
|
432
|
|
433 enum['values'][-1]['last'] = True
|
|
434
|
|
435 if 'documentation' in enum:
|
|
436 enum['has_documentation'] = True
|
|
437 enum['documentation'] = list(map(lambda x: { 'line' : x }, FixLinesWidth([ enum['documentation'] ])))
|
|
438
|
|
439 with open(os.path.join(SOURCE, 'JavaEnumeration.mustache'), 'r') as f:
|
|
440 template = f.read()
|
|
441
|
|
442 with open(os.path.join(TARGET, '%s.java' % enum['short_name']), 'w') as g:
|
|
443 g.write(renderer.render(template, enum))
|
|
444
|
|
445
|
|
446
|
|
447 for cls in model['classes']:
|
|
448 shortName = RemoveOrthancPluginPrefix(cls['name'], False)
|
|
449
|
|
450 with open(os.path.join(SOURCE, 'JavaClass.mustache'), 'r') as f:
|
|
451 template = f.read()
|
|
452
|
|
453 methods = []
|
|
454 for m in cls['methods']:
|
|
455 methods.append(EncodeFunction(shortName, m))
|
|
456
|
|
457 constructors = []
|
|
458 for f in nativeFunctions:
|
|
459 if (f['class_name'] == None and
|
|
460 f['return'].get('is_object') == True and
|
|
461 f['return']['class_name'] == cls['name']):
|
|
462 constructors.append(f)
|
|
463
|
|
464 with open(os.path.join(TARGET, '%s.java' % shortName), 'w') as g:
|
|
465 if not cls['name'] in classDocumentation:
|
|
466 raise Exception('No global documentation for class: %s' % cls['name'])
|
|
467
|
|
468 g.write(renderer.render(template, {
|
|
469 'destructor' : cls.get('destructor'),
|
|
470 'class_name' : shortName,
|
|
471 'methods' : methods,
|
|
472 'constructors' : constructors,
|
|
473 'has_documentation' : True,
|
|
474 'documentation' : classDocumentation[cls['name']],
|
|
475 }))
|