Mercurial > hg > orthanc-java
view CodeGeneration/CodeModel.py @ 59:1873eecbc592
auto-generation of C++ wrapper is now part of the build
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 14 Aug 2025 16:05:18 +0200 |
parents | |
children | cc91717e2354 |
line wrap: on
line source
# SPDX-FileCopyrightText: 2023-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # SPDX-License-Identifier: GPL-3.0-or-later # Java plugin for Orthanc # Copyright (C) 2023-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import json import re def ReadOrthancSdkDefaultVersion(path): with open(path, 'r') as f: m = re.findall(r'^set\(ORTHANC_SDK_DEFAULT_VERSION "([^"]+)"\)$', f.read(), re.MULTILINE) assert(len(m) == 1) return m[0] def ReadOrthancSdkVersion(path): with open(path, 'r') as f: content = f.read() major = re.findall(r'#\s*define\s+ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER\s+([0-9.]+)$', content, re.MULTILINE) minor = re.findall(r'#\s*define\s+ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER\s+([0-9.]+)$', content, re.MULTILINE) revision = re.findall(r'#\s*define\s+ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER\s+([0-9.]+)$', content, re.MULTILINE) assert(len(major) == 1) assert(len(minor) == 1) assert(len(revision) == 1) return [ int(major[0]), int(minor[0]), int(revision[0]) ] def IsPrimitiveAvailable(sdk_version, primitive, key_prefix = ''): since_sdk = primitive.get('since_sdk') if since_sdk != None: assert(len(since_sdk) == 3) assert(len(sdk_version) == 3) if since_sdk[0] < sdk_version[0]: available = True elif since_sdk[0] > sdk_version[0]: available = False elif since_sdk[1] < sdk_version[1]: available = True elif since_sdk[1] > sdk_version[1]: available = False else: available = since_sdk[2] <= sdk_version[2] if not available: name = primitive.get('name') if name == None: name = primitive.get('c_function') if name == None: name = primitive.get('short_name') if name == None: # For enumerations key = primitive.get('key') if key != None: name = '%s_%s' % (key_prefix, key) assert(name != None) print('Primitive unavailable in SDK: %s (only available since %s)' % (name, '.'.join(map(str, since_sdk)))) return available else: return True def ToUpperCase(name): s = '' for i in range(len(name)): if name[i].isupper(): if len(s) == 0: s += name[i] elif name[i - 1].islower(): s += '_' + name[i] elif (i + 1 < len(name) and name[i - 1].islower() and name[i + 1].isupper()): s += '_' + name[i] else: s += name[i] else: s += name[i].upper() return s def RemoveOrthancPluginPrefix(s, isCamelCase): PREFIX = 'OrthancPlugin' if s.startswith(PREFIX): t = s[len(PREFIX):] if isCamelCase: t = t[0].lower() + t[1:] return t else: raise Exception('Incorrect prefix: %s' % s) def ConvertReturnType(f): result = None if f['return_sdk_type'] == 'void': result = { 'c_type' : 'void', 'is_void' : True, 'java_signature' : 'V', 'java_type' : 'void', } elif f['return_sdk_type'] in [ 'int', 'int32_t', 'uint32_t' ]: result = { 'c_type' : 'jint', 'default_value' : '0', 'is_number' : True, 'java_signature' : 'I', 'java_type' : 'int', } elif f['return_sdk_type'] in [ 'int64_t' ]: result = { 'c_type' : 'jlong', 'default_value' : '0', 'is_number' : True, 'java_signature' : 'J', 'java_type' : 'long', } elif f['return_sdk_type'] == 'OrthancPluginMemoryBuffer *': result = { 'c_type' : 'jbyteArray', 'default_value' : 'NULL', 'is_bytes' : True, 'java_signature' : '[B', 'java_type' : 'byte[]', } elif f['return_sdk_type'] == 'enumeration': if f['return_sdk_enumeration'] == 'OrthancPluginErrorCode': result = { 'c_type' : 'void', 'is_exception' : True, 'java_signature' : 'V', 'java_type' : 'void', } else: result = { 'c_type' : 'jint', 'default_value' : '0', 'is_enumeration' : True, 'java_wrapper_type' : RemoveOrthancPluginPrefix(f['return_sdk_enumeration'], False), 'java_signature' : 'I', 'java_type' : 'int', } elif f['return_sdk_type'] == 'object': result = { 'c_type' : 'jlong', 'class_name' : f['return_sdk_class'], 'default_value' : '0', 'is_object' : True, 'java_wrapper_type' : RemoveOrthancPluginPrefix(f['return_sdk_class'], False), 'java_signature' : 'J', 'java_type' : 'long', } elif f['return_sdk_type'] == 'char *': result = { 'c_type' : 'jstring', 'default_value' : 'NULL', 'is_dynamic_string' : True, 'java_signature' : 'Ljava/lang/String;', 'java_type' : 'String', } elif f['return_sdk_type'] == 'const char *': result = { 'c_type' : 'jstring', 'default_value' : 'NULL', 'is_static_string' : True, 'java_signature' : 'Ljava/lang/String;', 'java_type' : 'String', } else: raise Exception('Unsupported return type: %s' % json.dumps(f, indent=4)) if not 'java_wrapper_type' in result: result['java_wrapper_type'] = result['java_type'] return result def ConvertArgument(arg): result = None if arg['sdk_type'] in [ 'int', 'int32_t', 'uint32_t' ]: result = { 'c_type' : 'jint', 'java_signature' : 'I', 'java_type' : 'int', } elif arg['sdk_type'] == 'uint8_t': result = { 'c_type' : 'jbyte', 'java_signature' : 'B', 'java_type' : 'byte', } elif arg['sdk_type'] == 'uint16_t': result = { 'c_type' : 'jshort', 'java_signature' : 'S', 'java_type' : 'short', } elif arg['sdk_type'] in [ 'int64_t', 'uint64_t' ]: result = { 'c_type' : 'jlong', 'java_signature' : 'J', 'java_type' : 'long', } elif arg['sdk_type'] == 'float': result = { 'c_type' : 'jfloat', 'java_signature' : 'F', 'java_type' : 'float', } elif arg['sdk_type'] == 'const char *': result = { 'c_accessor' : 'c_%s.GetValue()' % arg['name'], 'c_type' : 'jstring', 'convert_string' : True, 'java_signature' : 'Ljava/lang/String;', 'java_type' : 'String', } elif arg['sdk_type'] == 'const_void_pointer_with_size': # NB: The cast to "const char*" allows compatibility with functions whose # signatures were incorrect at the time they were introduced, notably: # - argument "body" of "OrthancPluginSendHttpStatus()" in 1.11.1 result = { 'c_accessor' : 'reinterpret_cast<const char*>(c_%s.GetData()), c_%s.GetSize()' % (arg['name'], arg['name']), 'c_type' : 'jbyteArray', 'convert_bytes' : True, 'java_signature' : '[B', 'java_type' : 'byte[]', } elif arg['sdk_type'] == 'enumeration': result = { 'c_accessor' : 'static_cast<%s>(%s)' % (arg['sdk_enumeration'], arg['name']), 'c_type' : 'jint', 'java_wrapper_accessor' : '%s.getValue()' % arg['sdk_name'], 'java_wrapper_type' : RemoveOrthancPluginPrefix(arg['sdk_enumeration'], False), 'java_signature' : 'I', 'java_type' : 'int', } elif arg['sdk_type'] == 'const void *': result = { 'c_accessor' : 'c_%s.GetData()' % arg['name'], 'c_type' : 'jbyteArray', 'convert_bytes' : True, 'java_signature' : '[B', 'java_type' : 'byte[]', } elif arg['sdk_type'] in [ 'object', 'const_object' ]: result = { 'c_accessor' : 'reinterpret_cast<%s*>(static_cast<intptr_t>(%s))' % (arg['sdk_class'], arg['name']), 'c_type' : 'jlong', 'java_signature' : 'J', 'java_type' : 'long', 'java_wrapper_accessor' : '%s.getSelf()' % arg['sdk_name'], 'java_wrapper_type' : RemoveOrthancPluginPrefix(arg['sdk_class'], False), } else: raise Exception('Unsupported argument type: %s' % json.dumps(arg, indent=4)) result['name'] = arg['name'] result['sdk_name'] = arg['sdk_name'] if not 'java_wrapper_type' in result: result['java_wrapper_type'] = result['java_type'] if not 'java_wrapper_accessor' in result: result['java_wrapper_accessor'] = arg['sdk_name'] if not 'c_accessor' in result: result['c_accessor'] = arg['name'] return result def FixLinesWidth(source): target = [] for line in source: for word in line.split(' '): if len(target) == 0: target.append(word) elif len(target[-1]) == 0: target[-1] = word elif len(target[-1] + ' ' + word) <= 80: target[-1] = target[-1] + ' ' + word else: target.append(word) target.append('') while len(target) > 0: if target[-1] == '': target = target[:-1] else: break return target def EncodeFunctionDocumentation(f): documentation = f['documentation'] paragraphs = [ ] if 'summary' in documentation: paragraphs.append(documentation['summary']) paragraphs.append('') if 'description' in documentation: for line in documentation['description']: paragraphs.append(line) paragraphs.append('') if 'args' in documentation: for arg in f['args']: name = arg['sdk_name'] if name in documentation['args']: doc = documentation['args'][name] paragraphs.append('@param %s %s' % (name, doc)) if 'return' in documentation: if (f['return_sdk_type'] == 'enumeration' and f['return_sdk_enumeration'] == 'OrthancPluginErrorCode'): pass elif f['return_sdk_type'] == 'object': paragraphs.append('@return The newly constructed object.') elif f['return_sdk_type'] in [ 'char *', 'const char *' ]: paragraphs.append('@return The resulting string.') elif f['return_sdk_type'] == 'OrthancPluginMemoryBuffer *': paragraphs.append('@return The resulting memory buffer.') else: paragraphs.append('@return ' + documentation['return']) lines = FixLinesWidth(paragraphs) return list(map(lambda x: { 'line' : x }, lines)) def WrapFunction(className, f): args = [] for a in f['args']: args.append(ConvertArgument(a)) if len(args) > 0: args[-1]['last'] = True returnType = ConvertReturnType(f) signature = '(%s%s)%s' % ('J' if className != None else '', ''.join(map(lambda x: x['java_signature'], args)), returnType['java_signature']) result = { 'args' : args, 'c_function' : f['c_function'], 'class_name' : className, 'has_args' : len(args) > 0, 'java_signature' : signature, 'return' : returnType, 'java_name' : RemoveOrthancPluginPrefix(f['c_function'], True), 'since_sdk' : f.get('since_sdk', None), } if 'documentation' in f: result['has_documentation'] = True result['documentation'] = EncodeFunctionDocumentation(f) if (returnType.get('is_number') == True or returnType.get('is_bytes') == True or returnType.get('is_dynamic_string') == True or returnType.get('is_static_string') == True): result['java_return_start'] = 'return ' elif returnType.get('is_enumeration') == True: result['java_return_start'] = 'return %s.getInstance(' % returnType['java_wrapper_type'] result['java_return_end'] = ')' elif returnType.get('is_object') == True: result['java_return_start'] = 'return new %s(' % returnType['java_wrapper_type'] result['java_return_end'] = ')' return result def Load(path): with open(path, 'r') as f: model = json.loads(f.read()) for enum in model['enumerations']: if not enum['name'].startswith('OrthancPlugin'): raise Exception() enum['short_name'] = enum['name'][len('OrthancPlugin'):] for i in range(len(enum['values'])): enum['values'][i]['key'] = ToUpperCase(enum['values'][i]['key']) if 'documentation' in enum['values'][i]: enum['values'][i]['has_documentation'] = True enum['values'][i]['documentation'] = list(map(lambda x: { 'line' : x }, FixLinesWidth([ enum['values'][i]['documentation'] ]))) enum['values'][-1]['last'] = True if 'documentation' in enum: enum['has_documentation'] = True enum['documentation'] = list(map(lambda x: { 'line' : x }, FixLinesWidth([ enum['documentation'] ]))) nativeFunctions = [] for f in model['global_functions']: nativeFunctions.append(WrapFunction(None, f)) for c in model['classes']: c['short_name'] = RemoveOrthancPluginPrefix(c['name'], False) if 'destructor' in c: nativeFunctions.append(WrapFunction(c['name'], { 'args' : [], 'c_function' : c['destructor'], 'return_sdk_type' : 'void', 'since_sdk' : c.get('since_sdk'), })) wrappedMethods = [] for m in c['methods']: nativeFunctions.append(WrapFunction(c['name'], m)) wrappedMethods.append(WrapFunction(c['short_name'], m)) c['wrapped_methods'] = wrappedMethods model['native_functions'] = nativeFunctions return model