Mercurial > hg > orthanc-java
changeset 65:156e942b136e
added support for custom methods
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 20 Aug 2025 13:01:39 +0200 |
parents | 9817ca245a49 |
children | b87ce7874ddc |
files | .reuse/dep5 CodeGeneration/CodeModel.py CodeGeneration/CppCodeGeneration.py CodeGeneration/CppNativeSDK.mustache CodeGeneration/CustomFunctions.json CodeGeneration/CustomMethods.json CodeGeneration/JavaCodeGeneration.py Plugin/JavaEnvironment.cpp Plugin/JavaEnvironment.h Plugin/Plugin.cpp |
diffstat | 10 files changed, 200 insertions(+), 28 deletions(-) [+] |
line wrap: on
line diff
--- a/.reuse/dep5 Thu Aug 14 18:18:19 2025 +0200 +++ b/.reuse/dep5 Wed Aug 20 13:01:39 2025 +0200 @@ -11,6 +11,8 @@ Samples/FHIR/NOTES.txt Samples/MammographyDeepLearning/NOTES.txt CodeGeneration/README.txt + CodeGeneration/CustomFunctions.json + CodeGeneration/CustomMethods.json Copyright: 2023-2025 Sebastien Jodogne, ICTEAM UCLouvain (Belgium) License: CC0-1.0
--- a/CodeGeneration/CodeModel.py Thu Aug 14 18:18:19 2025 +0200 +++ b/CodeGeneration/CodeModel.py Wed Aug 20 13:01:39 2025 +0200 @@ -97,15 +97,35 @@ return s +def RemovePrefix(prefix, s, isCamelCase): + assert(s.startswith(prefix)) + t = s[len(prefix):] + if isCamelCase: + return t[0].lower() + t[1:] + else: + return t + + def RemoveOrthancPluginPrefix(s, isCamelCase): + CUSTOM_PREFIX = 'OrthancPluginCustom_' + if s.startswith(CUSTOM_PREFIX): + return RemovePrefix(CUSTOM_PREFIX, s, isCamelCase) + PREFIX = 'OrthancPlugin' if s.startswith(PREFIX): - t = s[len(PREFIX):] - if isCamelCase: - t = t[0].lower() + t[1:] - return t + return RemovePrefix(PREFIX, s, isCamelCase) + + raise Exception('Incorrect prefix: %s' % s) + + +def GetJavaMethodName(className, f): + if 'short_name' in f: + return f['short_name'] + elif (className != None and + f['c_function'].startswith(className)): + return RemovePrefix(className, f['c_function'], True) else: - raise Exception('Incorrect prefix: %s' % s) + return RemoveOrthancPluginPrefix(f['c_function'], True) def ConvertReturnType(f): @@ -122,15 +142,15 @@ result = { 'c_type' : 'jint', 'default_value' : '0', - 'is_number' : True, + 'is_basic_type' : True, 'java_signature' : 'I', 'java_type' : 'int', } - elif f['return_sdk_type'] in [ 'int64_t' ]: + elif f['return_sdk_type'] in [ 'int64_t', 'uint64_t' ]: result = { 'c_type' : 'jlong', 'default_value' : '0', - 'is_number' : True, + 'is_basic_type' : True, 'java_signature' : 'J', 'java_type' : 'long', } @@ -185,6 +205,14 @@ 'java_signature' : 'Ljava/lang/String;', 'java_type' : 'String', } + elif f['return_sdk_type'] == 'bool': + result = { + 'c_type' : 'jboolean', + 'default_value' : 'false', + 'is_basic_type' : True, + 'java_signature' : 'Z', + 'java_type' : 'boolean', + } else: raise Exception('Unsupported return type: %s' % json.dumps(f, indent=4)) @@ -196,6 +224,11 @@ def ConvertArgument(arg): result = None + if 'name' in arg: + name = arg['name'] + else: + name = arg['sdk_name'] + if arg['sdk_type'] in [ 'int', 'int32_t', 'uint32_t' ]: result = { 'c_type' : 'jint', @@ -228,7 +261,7 @@ } elif arg['sdk_type'] == 'const char *': result = { - 'c_accessor' : 'c_%s.GetValue()' % arg['name'], + 'c_accessor' : 'c_%s.GetValue()' % name, 'c_type' : 'jstring', 'convert_string' : True, 'java_signature' : 'Ljava/lang/String;', @@ -240,7 +273,7 @@ # - 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_accessor' : 'reinterpret_cast<const char*>(c_%s.GetData()), c_%s.GetSize()' % (name, name), 'c_type' : 'jbyteArray', 'convert_bytes' : True, 'java_signature' : '[B', @@ -248,7 +281,7 @@ } elif arg['sdk_type'] == 'enumeration': result = { - 'c_accessor' : 'static_cast<%s>(%s)' % (arg['sdk_enumeration'], arg['name']), + 'c_accessor' : 'static_cast<%s>(%s)' % (arg['sdk_enumeration'], name), 'c_type' : 'jint', 'java_wrapper_accessor' : '%s.getValue()' % arg['sdk_name'], 'java_wrapper_type' : RemoveOrthancPluginPrefix(arg['sdk_enumeration'], False), @@ -257,7 +290,7 @@ } elif arg['sdk_type'] == 'const void *': result = { - 'c_accessor' : 'c_%s.GetData()' % arg['name'], + 'c_accessor' : 'c_%s.GetData()' % name, 'c_type' : 'jbyteArray', 'convert_bytes' : True, 'java_signature' : '[B', @@ -265,7 +298,7 @@ } 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_accessor' : 'reinterpret_cast<%s*>(static_cast<intptr_t>(%s))' % (arg['sdk_class'], name), 'c_type' : 'jlong', 'java_signature' : 'J', 'java_type' : 'long', @@ -275,8 +308,9 @@ else: raise Exception('Unsupported argument type: %s' % json.dumps(arg, indent=4)) - result['name'] = arg['name'] + result['name'] = name result['sdk_name'] = arg['sdk_name'] + result['sdk_type'] = arg['sdk_type'] if not 'java_wrapper_type' in result: result['java_wrapper_type'] = result['java_type'] @@ -285,7 +319,7 @@ result['java_wrapper_accessor'] = arg['sdk_name'] if not 'c_accessor' in result: - result['c_accessor'] = arg['name'] + result['c_accessor'] = name return result @@ -352,7 +386,12 @@ return list(map(lambda x: { 'line' : x }, lines)) -def WrapFunction(className, f): +def WrapFunction(cls, f, is_custom = False): + if cls == None: + className = None + else: + className = cls['name'] + args = [] for a in f['args']: args.append(ConvertArgument(a)) @@ -361,10 +400,15 @@ args[-1]['last'] = True returnType = ConvertReturnType(f) - signature = '(%s%s)%s' % ('J' if className != None else '', + signature = '(%s%s)%s' % ('J' if cls != None else '', ''.join(map(lambda x: x['java_signature'], args)), returnType['java_signature']) + since_sdk = f.get('since_sdk') + if (since_sdk == None and + cls != None): + since_sdk = cls.get('since_sdk') + result = { 'args' : args, 'c_function' : f['c_function'], @@ -372,15 +416,17 @@ 'has_args' : len(args) > 0, 'java_signature' : signature, 'return' : returnType, - 'java_name' : RemoveOrthancPluginPrefix(f['c_function'], True), - 'since_sdk' : f.get('since_sdk', None), + 'java_name' : GetJavaMethodName(className, f), + 'since_sdk' : since_sdk, + 'is_custom' : is_custom, + 'return_sdk_type' : f['return_sdk_type'], } if 'documentation' in f: result['has_documentation'] = True result['documentation'] = EncodeFunctionDocumentation(f) - if (returnType.get('is_number') == True or + if (returnType.get('is_basic_type') == True or returnType.get('is_bytes') == True or returnType.get('is_dynamic_string') == True or returnType.get('is_static_string') == True): @@ -397,10 +443,22 @@ return result -def Load(path): +def Load(path, customFunctionsPath = None, customMethodsPath = None): with open(path, 'r') as f: model = json.loads(f.read()) + if customMethodsPath == None: + customMethods = {} + else: + with open(customMethodsPath, 'r') as f: + customMethods = json.loads(f.read()) + + if customFunctionsPath == None: + customFunctions = {} + else: + with open(customFunctionsPath, 'r') as f: + customFunctions = json.loads(f.read()) + for enum in model['enumerations']: if not enum['name'].startswith('OrthancPlugin'): raise Exception() @@ -425,11 +483,14 @@ for f in model['global_functions']: nativeFunctions.append(WrapFunction(None, f)) + for f in customFunctions: + nativeFunctions.append(WrapFunction(None, f, is_custom = True)) + for c in model['classes']: c['short_name'] = RemoveOrthancPluginPrefix(c['name'], False) if 'destructor' in c: - nativeFunctions.append(WrapFunction(c['name'], { + nativeFunctions.append(WrapFunction(c, { 'args' : [], 'c_function' : c['destructor'], 'return_sdk_type' : 'void', @@ -439,8 +500,12 @@ wrappedMethods = [] for m in c['methods']: - nativeFunctions.append(WrapFunction(c['name'], m)) - wrappedMethods.append(WrapFunction(c['short_name'], m)) + nativeFunctions.append(WrapFunction(c, m)) + wrappedMethods.append(WrapFunction(c, m)) + + for customMethod in customMethods.get(c['name'], []): + nativeFunctions.append(WrapFunction(c, customMethod, is_custom = True)) + wrappedMethods.append(WrapFunction(c, customMethod, is_custom = True)) c['wrapped_methods'] = wrappedMethods
--- a/CodeGeneration/CppCodeGeneration.py Thu Aug 14 18:18:19 2025 +0200 +++ b/CodeGeneration/CppCodeGeneration.py Wed Aug 20 13:01:39 2025 +0200 @@ -50,6 +50,12 @@ parser.add_argument('--target', default = 'NativeSDK.cpp', help = 'Target file') +parser.add_argument('--custom-functions', + default = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'CustomFunctions.json'), + help = 'Input list of custom global functions') +parser.add_argument('--custom-methods', + default = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'CustomMethods.json'), + help = 'Input list of custom methods') args = parser.parse_args() @@ -63,7 +69,9 @@ print('** Generating the C++ native functions to wrap Orthanc SDK %d.%d.%d **' % (SDK_VERSION[0], SDK_VERSION[1], SDK_VERSION[2])) -model = CodeModel.Load(args.model) +model = CodeModel.Load(args.model, + customFunctionsPath = args.custom_functions, + customMethodsPath = args.custom_methods) with open(os.path.join(ROOT, 'CppNativeSDK.mustache'), 'r') as f: template = f.read()
--- a/CodeGeneration/CppNativeSDK.mustache Thu Aug 14 18:18:19 2025 +0200 +++ b/CodeGeneration/CppNativeSDK.mustache Wed Aug 20 13:01:39 2025 +0200 @@ -37,6 +37,14 @@ extern OrthancPluginContext* context_; {{#functions}} +{{#is_custom}} +{{#return.is_basic_type}} +extern {{return_sdk_type}} {{c_function}}(OrthancPluginContext* context{{#class_name}}, {{class_name}}* self{{/class_name}}{{#args}}, {{sdk_type}} {{name}}{{/args}}); +{{/return.is_basic_type}} +{{/is_custom}} +{{/functions}} + +{{#functions}} JNIEXPORT {{return.c_type}} JNI_{{c_function}}(JNIEnv* env, jobject sdkObject{{#class_name}}, jlong self{{/class_name}}{{#args}}, {{c_type}} {{name}}{{/args}}) { @@ -67,11 +75,11 @@ } {{/return.is_exception}} -{{#return.is_number}} +{{#return.is_basic_type}} return {{c_function}}(context_ {{#class_name}}, reinterpret_cast<{{class_name}}*>(static_cast<intptr_t>(self)){{/class_name}} {{#args}}, {{c_accessor}}{{/args}}); -{{/return.is_number}} +{{/return.is_basic_type}} {{#return.is_static_string}} const char* s = {{c_function}}(context_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CodeGeneration/CustomFunctions.json Wed Aug 20 13:01:39 2025 +0200 @@ -0,0 +1,20 @@ +[ + { + "c_function" : "OrthancPluginCustom_GetQueueSize", + "documentation" : { + "description" : [ "Get the number of elements in a queue." ], + "args" : { + "queueId" : "The identifier of the queue." + }, + "return" : "The value, or None." + }, + "args" : [ + { + "sdk_name" : "queueId", + "sdk_type" : "const char *" + } + ], + "return_sdk_type" : "uint64_t", + "since_sdk" : [ 1, 12, 8 ] + } +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CodeGeneration/CustomMethods.json Wed Aug 20 13:01:39 2025 +0200 @@ -0,0 +1,15 @@ +{ + "OrthancPluginKeysValuesIterator" : [ + { + "short_name" : "next", + "c_function" : "OrthancPluginCustom_KeysValuesIteratorNext", + "documentation" : { + "description" : [ "Advance to the next element in the iterator." ], + "return" : "Whether the iterator is done." + }, + "args" : [ + ], + "return_sdk_type" : "bool" + } + ] +}
--- a/CodeGeneration/JavaCodeGeneration.py Thu Aug 14 18:18:19 2025 +0200 +++ b/CodeGeneration/JavaCodeGeneration.py Wed Aug 20 13:01:39 2025 +0200 @@ -50,6 +50,12 @@ parser.add_argument('--target', default = '/tmp/OrthancJavaSDK', help = 'Target folder') +parser.add_argument('--custom-functions', + default = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'CustomFunctions.json'), + help = 'Input list of custom global functions') +parser.add_argument('--custom-methods', + default = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'CustomMethods.json'), + help = 'Input list of custom methods') args = parser.parse_args() @@ -68,7 +74,9 @@ escape = lambda u: u, # No escaping ) -model = CodeModel.Load(args.model) +model = CodeModel.Load(args.model, + customFunctionsPath = args.custom_functions, + customMethodsPath = args.custom_methods) print('** Generating the Java classes to wrap Orthanc SDK %d.%d.%d **' % (SDK_VERSION[0], SDK_VERSION[1], SDK_VERSION[2]))
--- a/Plugin/JavaEnvironment.cpp Thu Aug 14 18:18:19 2025 +0200 +++ b/Plugin/JavaEnvironment.cpp Wed Aug 20 13:01:39 2025 +0200 @@ -296,3 +296,10 @@ throw std::runtime_error("Cannot create enumeration value: " + fqn + " " + buf); } } + + +std::string JavaEnvironment::GetRuntimeErrorMessage(OrthancPluginContext* context, + OrthancPluginErrorCode code) +{ + return "An exception has occurred in Java: " + std::string(OrthancPluginGetErrorDescription(context, code)); +}
--- a/Plugin/JavaEnvironment.h Thu Aug 14 18:18:19 2025 +0200 +++ b/Plugin/JavaEnvironment.h Wed Aug 20 13:01:39 2025 +0200 @@ -87,4 +87,7 @@ jobject ConstructEnumValue(const std::string& fqn, int value); + + static std::string GetRuntimeErrorMessage(OrthancPluginContext* context, + OrthancPluginErrorCode code); };
--- a/Plugin/Plugin.cpp Thu Aug 14 18:18:19 2025 +0200 +++ b/Plugin/Plugin.cpp Wed Aug 20 13:01:39 2025 +0200 @@ -435,6 +435,42 @@ } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8) +uint64_t OrthancPluginCustom_GetQueueSize(OrthancPluginContext* context, const char *queueId) +{ + uint64_t size = 0; + OrthancPluginErrorCode code = OrthancPluginGetQueueSize(context, queueId, &size); + + if (code == OrthancPluginErrorCode_Success) + { + return size; + } + else + { + throw std::runtime_error(JavaEnvironment::GetRuntimeErrorMessage(context, code)); + } +} +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8) +bool OrthancPluginCustom_KeysValuesIteratorNext(OrthancPluginContext* context, OrthancPluginKeysValuesIterator* self) +{ + uint8_t done = true; + OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorNext(context, &done, self); + + if (code == OrthancPluginErrorCode_Success) + { + return done; + } + else + { + throw std::runtime_error(JavaEnvironment::GetRuntimeErrorMessage(context, code)); + } +} +#endif + + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)