Mercurial > hg > orthanc-java
comparison CodeGeneration/CodeGeneration.py @ 0:3ecef5782f2c
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 18 Oct 2023 17:59:44 +0200 |
parents | |
children | 15dc698243ac |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:3ecef5782f2c |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # SPDX-FileCopyrightText: 2023 Sebastien Jodogne, UCLouvain, Belgium | |
4 # SPDX-License-Identifier: GPL-3.0-or-later | |
5 | |
6 # Java plugin for Orthanc | |
7 # Copyright (C) 2023 Sebastien Jodogne, UCLouvain, Belgium | |
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 })) |