diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CodeGeneration/CodeGeneration.py	Wed Oct 18 17:59:44 2023 +0200
@@ -0,0 +1,475 @@
+#!/usr/bin/env python3
+
+# SPDX-FileCopyrightText: 2023 Sebastien Jodogne, UCLouvain, Belgium
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# Java plugin for Orthanc
+# Copyright (C) 2023 Sebastien Jodogne, 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 argparse
+import json
+import os
+import pystache
+
+
+SOURCE = os.path.abspath(os.path.dirname(__file__))
+
+parser = argparse.ArgumentParser(description = 'Generate Java wrapper from code model.')
+parser.add_argument('--source',
+                    default = os.path.join(SOURCE, 'CodeModel.json'),
+                    help = 'location of the JSON code model')
+
+args = parser.parse_args()
+
+
+
+TARGET = os.path.join(SOURCE, '..', 'JavaSDK', 'be', 'uclouvain', 'orthanc')
+
+with open(args.source, 'r') as f:
+    model = json.loads(f.read())
+
+with open(os.path.join(SOURCE, 'ClassDocumentation.json'), 'r') as f:
+    classDocumentation = json.loads(f.read())
+
+    
+renderer = pystache.Renderer(
+    escape = lambda u: u,  # No escaping
+)
+
+
+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'] == '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':
+        result = {
+            'c_accessor' : '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 EncodeFunction(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),
+    }
+
+    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
+
+
+nativeFunctions = []
+
+for f in model['global_functions']:
+    nativeFunctions.append(EncodeFunction(None, f))
+
+
+for c in model['classes']:
+    if 'destructor' in c:
+        nativeFunctions.append(EncodeFunction(c['name'], {
+            'args' : [],
+            'c_function' : c['destructor'],
+            'return_sdk_type' : 'void',
+        }))
+
+    for m in c['methods']:
+        nativeFunctions.append(EncodeFunction(c['name'], m))
+        
+
+with open(os.path.join(SOURCE, 'JavaNativeSDK.mustache'), 'r') as f:
+    template = f.read()
+
+    with open(os.path.join(TARGET, 'NativeSDK.java'), 'w') as g:
+        g.write(renderer.render(template, {
+            'functions' : nativeFunctions
+        }))
+
+
+with open(os.path.join(SOURCE, 'JavaFunctions.mustache'), 'r') as f:
+    template = f.read()
+
+    with open(os.path.join(TARGET, 'Functions.java'), 'w') as g:
+        g.write(renderer.render(template, {
+            'functions' : filter(lambda x: (x['class_name'] == None and
+                                            x['return'].get('is_object') != True), nativeFunctions),
+        }))
+
+
+with open(os.path.join(SOURCE, 'CppNativeSDK.mustache'), 'r') as f:
+    template = f.read()
+
+    with open(os.path.join(SOURCE, '..', 'Plugin', 'NativeSDK.cpp'), 'w') as g:
+        s = renderer.render(template, {
+            'functions' : nativeFunctions
+        })
+
+        s = s.splitlines()
+        s = filter(lambda l: not l.isspace() or len(l) == 0, s)
+        
+        g.write('\n'.join(s))
+
+        
+
+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'] ])))
+
+    with open(os.path.join(SOURCE, 'JavaEnumeration.mustache'), 'r') as f:
+        template = f.read()
+        
+        with open(os.path.join(TARGET, '%s.java' % enum['short_name']), 'w') as g:
+            g.write(renderer.render(template, enum))
+
+
+
+for cls in model['classes']:
+    shortName = RemoveOrthancPluginPrefix(cls['name'], False)
+
+    with open(os.path.join(SOURCE, 'JavaClass.mustache'), 'r') as f:
+        template = f.read()
+
+        methods = []
+        for m in cls['methods']:
+            methods.append(EncodeFunction(shortName, m))
+
+        constructors = []
+        for f in nativeFunctions:
+            if (f['class_name'] == None and
+                f['return'].get('is_object') == True and
+                f['return']['class_name'] == cls['name']):
+                constructors.append(f)
+
+        with open(os.path.join(TARGET, '%s.java' % shortName), 'w') as g:
+            if not cls['name'] in classDocumentation:
+                raise Exception('No global documentation for class: %s' % cls['name'])
+
+            g.write(renderer.render(template, {
+                'destructor' : cls.get('destructor'),
+                'class_name' : shortName,
+                'methods' : methods,
+                'constructors' : constructors,
+                'has_documentation' : True,
+                'documentation' : classDocumentation[cls['name']],
+            }))