genwrapper.py 37.9 KB
Newer Older
Sylvain Berfini's avatar
Sylvain Berfini committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#!/usr/bin/python

# Copyright (C) 2017 Belledonne Communications SARL
#
# 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 2
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

import argparse
import os
import sys
import pystache
import errno

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'tools'))
print(sys.path)
import genapixml as CApi
import abstractapi as AbsApi
import metadoc

##########################################################################

34 35 36 37 38 39 40 41 42 43 44
ENUMS_LIST = {
    'AccountCreatorActivationCodeStatus': 'AccountCreator',
    'AccountCreatorDomainStatus': 'AccountCreator',
    'AccountCreatorEmailStatus': 'AccountCreator',
    'AccountCreatorLanguageStatus': 'AccountCreator',
    'AccountCreatorPasswordStatus': 'AccountCreator',
    'AccountCreatorPhoneNumberStatus': 'AccountCreator',
    'AccountCreatorStatus': 'AccountCreator',
    'AccountCreatorTransportStatus': 'AccountCreator',
    'AccountCreatorUsernameStatus': 'AccountCreator',
    'AddressFamily': 'CallStats',
45
    'AuthMethod': 'Core',
46 47 48
    'CallDir': 'Call',
    'CallState': 'Call',
    'CallStatus': 'CallLog',
49
    'ChatRoomState': 'ChatRoom',
50 51 52
    'ChatMessageState': 'ChatMessage',
    'ConfiguringState': 'Core',
    'CoreLogCollectionUploadState': 'Core',
53
    'EcCalibratorStatus': 'Core',
54
    'EventLogType': 'EventLog',
55 56 57 58 59 60 61 62 63 64
    'GlobalState': 'Core',
    'FriendListStatus': 'FriendList',
    'IceState': 'CallStats',
    'LimeState': 'Core',
    'MediaDirection': 'Core',
    'MediaEncryption': 'Core',
    'PlayerState': 'Player',
    'RegistrationState': 'Core',
    'SubscribePolicy': 'Friend',
    'TransportType': 'Address',
65
    'TunnelMode': 'Tunnel',
66 67
    'XmlRpcRequestArgType': 'XmlRpcRequest',
    'XmlRpcRequestStatus': 'XmlRpcRequest',
68 69 70 71
}

##########################################################################

Sylvain Berfini's avatar
Sylvain Berfini committed
72
class JavaTranslator(object):
73 74
    def __init__(self, packageName, exceptions):
        self.exceptions = exceptions
75 76
        package_dirs = packageName.split('.')
        self.jni_package = ''
77
        self.jni_path = ''
78 79
        for directory in package_dirs:
            self.jni_package += directory + '_'
80
            self.jni_path += directory + '/'
81

Sylvain Berfini's avatar
Sylvain Berfini committed
82 83 84
        self.docTranslator = metadoc.SandcastleJavaTranslator()

    def throws_exception(self, _type):
85 86
        if not self.exceptions:
            return False
Sylvain Berfini's avatar
Sylvain Berfini committed
87 88 89 90 91 92 93 94
        if type(_type) is AbsApi.BaseType:
            if _type.name == 'status':
                return True
        return False

    def translate_argument_name(self, _argName):
        return _argName.to_snake_case()

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
    def translate_java_jni_enum_name(self, _enum):
        name = _enum.name.to_camel_case()
        if name in ENUMS_LIST:
            className = ENUMS_LIST[name]
            if name.startswith(className):
                name = name[len(className):]
            name = className + '$' + name
        return name

    def translate_java_jni_base_type_name(self, _type):
        if _type == 'string':
            return 'Ljava/lang/String;'
        elif _type == 'integer':
            return 'I'
        elif _type == 'boolean':
            return 'Z'
        elif _type == 'floatant':
            return 'F'
        elif _type == 'size':
            return 'I'
        elif _type == 'time':
            return 'I'
        elif _type == 'status':
            return 'I'
        elif _type == 'string_array':
            return '[Ljava/lang/String;'
        elif _type == 'character':
            return 'C'
        elif _type == 'void':
            return 'V'
        return _type

127 128
    def translate_as_c_base_type(self, t):
        _type = t.name
129
        if _type == 'string':
130
            return 'char *'
131
        elif _type == 'integer':
132 133 134 135 136 137 138 139 140
            if t.size is None:
                if t.isUnsigned:
                    return 'unsigned int'
                return 'int'
            inttype = 'int{0}_t'.format(t.size)
            if t.isUnsigned:
                inttype = 'u' + inttype
            if t.isref:
                inttype = inttype + ' *'
141 142
            if t.isconst:
                inttype = 'const ' + inttype
143
            return inttype
144 145 146 147 148 149 150 151 152 153 154
        elif _type == 'boolean':
            return 'bool_t'
        elif _type == 'floatant':
            return 'float'
        elif _type == 'size':
            return 'size_t'
        elif _type == 'time':
            return 'time_t'
        elif _type == 'status':
            return 'int'
        elif _type == 'string_array':
155
            return 'char **'
156 157 158
        elif _type == 'character':
            return 'char'
        elif _type == 'void':
159 160 161
            if t.isref:
                return 'void *'
            return 'void'
162 163
        return _type

Sylvain Berfini's avatar
Sylvain Berfini committed
164
    def translate_type(self, _type, native=False, jni=False, isReturn=False):
Sylvain Berfini's avatar
Sylvain Berfini committed
165
        if type(_type) is AbsApi.ListType:
166
            if jni:
Sylvain Berfini's avatar
Sylvain Berfini committed
167 168 169
                if type(_type.containedTypeDesc) is AbsApi.ClassType:
                    return 'jobjectArray'
                elif type(_type.containedTypeDesc) is AbsApi.BaseType:
170 171
                    if _type.containedTypeDesc.name == 'string':
                        return 'jobjectArray'
Sylvain Berfini's avatar
Sylvain Berfini committed
172 173 174
                    return self.translate_type(_type.containedTypeDesc, jni=True) + 'Array'
                elif type(_type.containedTypeDesc) is AbsApi.EnumType:
                    ptrtype = self.translate_type(_type.containedTypeDesc, native)
Sylvain Berfini's avatar
Sylvain Berfini committed
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
            ptrtype = ''
            if type(_type.containedTypeDesc) is AbsApi.ClassType:
                ptrtype = self.translate_type(_type.containedTypeDesc, native)
            elif type(_type.containedTypeDesc) is AbsApi.BaseType:
                ptrtype = self.translate_type(_type.containedTypeDesc, native)
            elif type(_type.containedTypeDesc) is AbsApi.EnumType:
                ptrtype = self.translate_type(_type.containedTypeDesc, native)
            else:
                if _type.containedTypeDesc:
                    raise AbsApi.Error('translation of bctbx_list_t of ' + _type.containedTypeDesc.name)
                else:
                    raise AbsApi.Error('translation of bctbx_list_t of unknow type !')
            return ptrtype + '[]'
                
        elif type(_type) is AbsApi.ClassType:
190
            if jni:
191
                return 'jobject'
Sylvain Berfini's avatar
Sylvain Berfini committed
192 193 194 195
            return _type.desc.name.to_camel_case()
        elif type(_type) is AbsApi.EnumType:
            if native:
                return 'int'
196 197
            elif jni:
                return 'jint'
198 199
            if _type.desc.name.to_camel_case() == "XmlRpcStatus":
                return "XmlRpcRequest.Status"
200 201
            elif _type.desc.name.to_camel_case() == "XmlRpcArgType":
                return "XmlRpcRequest.ArgType"
202 203 204 205
            name = _type.desc.name.to_camel_case()
            if name in ENUMS_LIST:
                className = ENUMS_LIST[name]
                if name.startswith(className):
206 207
                    name = name[len(className):]
                name = className + '.' + name
208
            return name
Sylvain Berfini's avatar
Sylvain Berfini committed
209 210
        elif type(_type) is AbsApi.BaseType:
            if _type.name == 'string':
211 212
                if jni:
                    return 'jstring'
Sylvain Berfini's avatar
Sylvain Berfini committed
213 214
                return 'String'
            elif _type.name == 'integer':
215 216 217 218
                if _type.size is not None and _type.isref:
                    if jni:
                        return 'jbyteArray'
                    return 'byte[]'
219 220
                if jni:
                    return 'jint'
Sylvain Berfini's avatar
Sylvain Berfini committed
221
                return 'int'
Sylvain Berfini's avatar
Sylvain Berfini committed
222 223 224 225
            elif _type.name == 'boolean':
                if jni:
                    return 'jboolean'
                return 'boolean'
Sylvain Berfini's avatar
Sylvain Berfini committed
226
            elif _type.name == 'floatant':
227 228
                if jni:
                    return 'jfloat'
Sylvain Berfini's avatar
Sylvain Berfini committed
229 230
                return 'float'
            elif _type.name == 'size':
231 232
                if jni:
                    return 'jint'
Sylvain Berfini's avatar
Sylvain Berfini committed
233
                return 'int'
Sylvain Berfini's avatar
Sylvain Berfini committed
234
            elif _type.name == 'time':
235 236
                if jni:
                    return 'jlong'
Sylvain Berfini's avatar
Sylvain Berfini committed
237
                return 'long'
238
            elif _type.name == 'status':
239 240
                if jni:
                    return 'jint'
241 242 243
                if native:
                    return 'int'
                return 'void'
244 245 246 247
            elif _type.name == 'string_array':
                if jni:
                    return 'jobjectArray'
                return 'String[]'
248 249 250 251
            elif _type.name == 'character':
                if jni:
                    return 'jchar'
                return 'char'
252
            elif _type.name == 'void':
253 254
                if isReturn:
                    return 'void'
255
                if jni:
256
                    return 'jobject'
257
                return 'Object'
Sylvain Berfini's avatar
Sylvain Berfini committed
258 259
            return _type.name

260 261
    def translate_argument(self, _arg, native=False, jni=False):
        return '{0} {1}'.format(self.translate_type(_arg.type, native, jni), self.translate_argument_name(_arg.name))
Sylvain Berfini's avatar
Sylvain Berfini committed
262 263 264 265 266 267 268 269 270

    def translate_property(self, _property):
        properties = []
        if _property.getter is not None:
            properties.append(self.translate_method(_property.getter))
        if _property.setter is not None:
            properties.append(self.translate_method(_property.setter))
        return properties

Sylvain Berfini's avatar
Sylvain Berfini committed
271
    def translate_jni_property(self, className, _property):
272 273
        properties = []
        if _property.getter is not None:
Sylvain Berfini's avatar
Sylvain Berfini committed
274
            properties.append(self.translate_jni_method(className, _property.getter))
275
        if _property.setter is not None:
Sylvain Berfini's avatar
Sylvain Berfini committed
276
            properties.append(self.translate_jni_method(className, _property.setter))
277 278
        return properties

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
    def generate_listener(self, name, _class):
        methodDict = {}
        methodDict['return'] = 'void'
        methodDict['return_native'] = 'void'
        methodDict['return_keyword'] = ''
        methodDict['convertInputClassArrayToLongArray'] = False
        methodDict['name'] = name
        methodDict['exception'] = False
        methodDict['enumCast'] = False
        methodDict['classCast'] = False

        methodDict['params'] = _class.name.to_camel_case() + 'Listener listener'
        methodDict['native_params'] = 'long nativePtr, ' + _class.name.to_camel_case() + 'Listener listener'
        methodDict['static_native_params'] = ''
        methodDict['native_params_impl'] = ', listener'

        methodDict['deprecated'] = False
        methodDict['doc'] = None

        return methodDict

    def generate_add_listener(self, _class):
        return self.generate_listener('addListener', _class)

    def generate_remove_listener(self, _class):
        return self.generate_listener('removeListener', _class)

    def generate_set_listener(self, _class):
        return self.generate_listener('setListener', _class)

Sylvain Berfini's avatar
Sylvain Berfini committed
309 310 311
    def translate_method(self, _method):
        methodDict = {}

Sylvain Berfini's avatar
Sylvain Berfini committed
312 313
        methodDict['return'] = self.translate_type(_method.returnType, isReturn=True)
        methodDict['return_native'] = self.translate_type(_method.returnType, native=True, isReturn=True)
Sylvain Berfini's avatar
Sylvain Berfini committed
314
        methodDict['return_keyword'] = '' if methodDict['return'] == 'void' else 'return '
315
        methodDict['hasReturn'] = not methodDict['return'] == 'void'
Sylvain Berfini's avatar
Sylvain Berfini committed
316 317 318 319 320 321

        methodDict['convertInputClassArrayToLongArray'] = False

        methodDict['name'] = _method.name.to_camel_case(lower=True)
        methodDict['exception'] = self.throws_exception(_method.returnType)

322
        methodDict['enumCast'] = type(_method.returnType) is AbsApi.EnumType
323
        methodDict['classCast'] = type(_method.returnType) is AbsApi.ClassType
324

Sylvain Berfini's avatar
Sylvain Berfini committed
325 326 327 328 329 330 331
        methodDict['params'] = ''
        methodDict['native_params'] = 'long nativePtr'
        methodDict['static_native_params'] = ''
        methodDict['native_params_impl'] = ''
        for arg in _method.args:
            if arg is not _method.args[0]:
                methodDict['params'] += ', '
332
                methodDict['static_native_params'] += ', '
Sylvain Berfini's avatar
Sylvain Berfini committed
333 334 335 336 337 338
            methodDict['native_params'] += ', '
            methodDict['native_params_impl'] += ', '

            methodDict['params'] += self.translate_argument(arg)
            methodDict['native_params'] += self.translate_argument(arg, True)
            methodDict['static_native_params'] += self.translate_argument(arg, True)
Sylvain Berfini's avatar
Sylvain Berfini committed
339
            if type(arg.type) is AbsApi.EnumType:
340
                methodDict['native_params_impl'] += self.translate_argument_name(arg.name) + '.toInt()'
Sylvain Berfini's avatar
Sylvain Berfini committed
341 342 343 344 345 346 347 348
            else:
                methodDict['native_params_impl'] += self.translate_argument_name(arg.name)

        methodDict['deprecated'] = _method.deprecated
        methodDict['doc'] = self.docTranslator.translate(_method.briefDescription) if _method.briefDescription is not None else None

        return methodDict

Sylvain Berfini's avatar
Sylvain Berfini committed
349
    def translate_jni_method(self, className, _method, static=False):
350
        methodDict = {}
Sylvain Berfini's avatar
Sylvain Berfini committed
351 352
        methodDict['classCName'] = 'Linphone' + className.to_camel_case()
        methodDict['className'] = className.to_camel_case()
353 354
        methodDict['classImplName'] = className.to_camel_case() + 'Impl'
        methodDict['jniPath'] = self.jni_path
Sylvain Berfini's avatar
Sylvain Berfini committed
355 356

        methodDict['return'] = self.translate_type(_method.returnType, jni=True, isReturn=True)
357
        methodDict['hasListReturn'] = methodDict['return'] == 'jobjectArray'
358 359
        methodDict['hasByteArrayReturn'] = methodDict['return'] == 'jbyteArray'
        methodDict['hasReturn'] = not methodDict['return'] == 'void' and not methodDict['hasListReturn'] and not methodDict['hasByteArrayReturn']
360
        methodDict['hasStringReturn'] = methodDict['return'] == 'jstring'
361
        methodDict['hasNormalReturn'] = not methodDict['hasListReturn'] and not methodDict['hasStringReturn'] and not methodDict['hasByteArrayReturn']
Sylvain Berfini's avatar
Sylvain Berfini committed
362 363 364 365 366
        methodDict['name'] = 'Java_' + self.jni_package + className.to_camel_case() + 'Impl_' + _method.name.to_camel_case(lower=True)
        methodDict['notStatic'] = not static
        methodDict['c_name'] = 'linphone_' + className.to_snake_case() + "_" + _method.name.to_snake_case()
        methodDict['returnObject'] = methodDict['hasReturn'] and type(_method.returnType) is AbsApi.ClassType
        methodDict['returnClassName'] = self.translate_type(_method.returnType)
367 368
        methodDict['isRealObjectArray'] = False
        methodDict['isStringObjectArray'] = False
369
        methodDict['c_type_return'] = self.translate_as_c_base_type(_method.returnType)
370 371

        if methodDict['hasListReturn']:
372 373 374
            if type(_method.returnType) is AbsApi.BaseType and _method.returnType.name == 'string_array':
                methodDict['isStringObjectArray'] = True
            elif type(_method.returnType.containedTypeDesc) is AbsApi.BaseType:
375 376 377
                methodDict['isStringObjectArray'] = True
            elif type(_method.returnType.containedTypeDesc) is AbsApi.ClassType:
                methodDict['isRealObjectArray'] = True
378
                methodDict['objectCPrefix'] = 'linphone_' + _method.returnType.containedTypeDesc.desc.name.to_snake_case()
379 380 381
                methodDict['objectClassCName'] = 'Linphone' + _method.returnType.containedTypeDesc.desc.name.to_camel_case()
                methodDict['objectClassName'] = _method.returnType.containedTypeDesc.desc.name.to_camel_case()
                methodDict['objectClassImplName'] = _method.returnType.containedTypeDesc.desc.name.to_camel_case() + 'Impl'
Sylvain Berfini's avatar
Sylvain Berfini committed
382

383
        methodDict['params'] = 'JNIEnv *env, jobject thiz, jlong ptr'
Sylvain Berfini's avatar
Sylvain Berfini committed
384 385 386
        methodDict['params_impl'] = ''
        methodDict['strings'] = []
        methodDict['objects'] = []
387 388
        methodDict['lists'] = []
        methodDict['array'] = []
389
        methodDict['bytes'] = []
Sylvain Berfini's avatar
Sylvain Berfini committed
390
        methodDict['returnedObjectGetter'] = ''
391
        for arg in _method.args:
Sylvain Berfini's avatar
Sylvain Berfini committed
392
            methodDict['params'] += ', '
393 394 395 396 397 398
            if static:
                if arg is not _method.args[0]:
                    methodDict['params_impl'] += ', '
            else:
                methodDict['params_impl'] += ', '

399
            methodDict['params'] += self.translate_argument(arg, jni=True)
Sylvain Berfini's avatar
Sylvain Berfini committed
400 401 402
            argname = self.translate_argument_name(arg.name)

            if type(arg.type) is AbsApi.ClassType:
403 404 405 406
                classCName = 'Linphone' + arg.type.desc.name.to_camel_case()
                if classCName[-8:] == 'Listener':
                   classCName = 'Linphone' + arg.type.desc.name.to_camel_case()[:-8] + 'Cbs'
                methodDict['objects'].append({'object': argname, 'objectClassCName': classCName})
Sylvain Berfini's avatar
Sylvain Berfini committed
407 408
                methodDict['params_impl'] += 'c_' + argname
                
409 410 411
            elif type(arg.type) is AbsApi.ListType:
                isStringList = type(arg.type.containedTypeDesc) is AbsApi.BaseType and arg.type.containedTypeDesc.name == 'string'
                isObjList = type(arg.type.containedTypeDesc) is AbsApi.ClassType
412
                methodDict['lists'].append({'list': argname, 'isStringList': isStringList, 'isObjList': isObjList, 'objectClassCName': arg.type.containedTypeDesc.name})
413
                methodDict['params_impl'] += 'bctbx_list_' + argname
414 415 416 417

            elif type(arg.type) is AbsApi.EnumType:
                argCType = arg.type.name
                methodDict['params_impl'] += '(' + argCType + ') ' + argname
418
                
Sylvain Berfini's avatar
Sylvain Berfini committed
419
            elif type(arg.type) is AbsApi.BaseType:
420
                if arg.type.name == 'integer' and arg.type.size is not None and arg.type.isref:
421 422 423
                    methodDict['bytes'].append({'bytesargname': argname, 'bytesargtype' : self.translate_as_c_base_type(arg.type)})
                    methodDict['params_impl'] += 'c_' + argname
                elif arg.type.name == 'string':
Sylvain Berfini's avatar
Sylvain Berfini committed
424 425 426
                    methodDict['strings'].append({'string': argname})
                    methodDict['params_impl'] += 'c_' + argname
                else:
427
                    methodDict['params_impl'] += '(' + self.translate_as_c_base_type(arg.type) + ')' + argname                        
Sylvain Berfini's avatar
Sylvain Berfini committed
428 429
            else:
                methodDict['params_impl'] += argname
430 431 432

        return methodDict

Sylvain Berfini's avatar
Sylvain Berfini committed
433 434
    def translate_class(self, _class):
        classDict = {
Sylvain Berfini's avatar
Sylvain Berfini committed
435 436
            'methods': [],
            'jniMethods': [],
Sylvain Berfini's avatar
Sylvain Berfini committed
437 438 439
        }

        classDict['isLinphoneFactory'] = _class.name.to_camel_case() == "Factory"
440
        classDict['isLinphoneCore'] = _class.name.to_camel_case() == "Core"
Sylvain Berfini's avatar
Sylvain Berfini committed
441 442 443 444 445
        classDict['doc'] = self.docTranslator.translate(_class.briefDescription) if _class.briefDescription is not None else None

        for _property in _class.properties:
            try:
                classDict['methods'] += self.translate_property(_property)
Sylvain Berfini's avatar
Sylvain Berfini committed
446
                classDict['jniMethods'] += self.translate_jni_property(_class.name, _property)
Sylvain Berfini's avatar
Sylvain Berfini committed
447 448 449 450 451 452
            except AbsApi.Error as e:
                print('error while translating {0} property: {1}'.format(_property.name.to_snake_case(), e.args[0]))

        for method in _class.instanceMethods:
            try:
                methodDict = self.translate_method(method)
Sylvain Berfini's avatar
Sylvain Berfini committed
453
                jniMethodDict = self.translate_jni_method(_class.name, method)
Sylvain Berfini's avatar
Sylvain Berfini committed
454
                classDict['methods'].append(methodDict)
455
                classDict['jniMethods'].append(jniMethodDict)
Sylvain Berfini's avatar
Sylvain Berfini committed
456 457 458 459 460 461
            except AbsApi.Error as e:
                print('Could not translate {0}: {1}'.format(method.name.to_snake_case(fullName=True), e.args[0]))

        for method in _class.classMethods:
            try:
                methodDict = self.translate_method(method)
Sylvain Berfini's avatar
Sylvain Berfini committed
462
                jniMethodDict = self.translate_jni_method(_class.name, method, True)
463
                classDict['methods'].append(methodDict)
464
                classDict['jniMethods'].append(jniMethodDict)
Sylvain Berfini's avatar
Sylvain Berfini committed
465 466 467
            except AbsApi.Error as e:
                print('Could not translate {0}: {1}'.format(method.name.to_snake_case(fullName=True), e.args[0]))

468 469 470 471 472 473 474 475 476
        islistenable = _class.listenerInterface is not None
        if islistenable:
            isMultiListener = (_class.multilistener)
            if isMultiListener:
                classDict['methods'].append(self.generate_add_listener(_class))
                classDict['methods'].append(self.generate_remove_listener(_class))
            else:
                classDict['methods'].append(self.generate_set_listener(_class))

Sylvain Berfini's avatar
Sylvain Berfini committed
477 478
        return classDict

479
    def translate_jni_interface(self, _class, className, _method):
480 481 482 483 484 485
        methodDict = {}
        listenerName = 'Linphone' + className.to_camel_case()
        methodDict['classCName'] = listenerName[:-8] #Remove Listener at the end
        methodDict['className'] = className.to_camel_case()
        methodDict['classImplName'] = className.to_camel_case() + 'Impl'
        methodDict['jniPath'] = self.jni_path
486
        methodDict['cPrefix'] = 'linphone_' + className.to_snake_case()[:-9] # Remove _listener at the end
487 488
        methodDict['callbackName'] = methodDict['cPrefix'] + '_' + _method.name.to_snake_case()
        methodDict['jname'] = _method.name.to_camel_case(lower=True)
489
        methodDict['return'] = self.translate_as_c_base_type(_method.returnType)
490 491 492
        methodDict['jniUpcallMethod'] = 'CallVoidMethod'
        methodDict['isJniUpcallBasicType'] = False
        methodDict['isJniUpcallObject'] = False
493 494
        if type(_method.returnType) is AbsApi.ClassType:
            methodDict['return'] += '*'
495 496 497 498
            methodDict['jniUpcallMethod'] = 'CallObjectMethod'
            methodDict['isJniUpcallObject'] = True
            methodDict['jniUpcallType'] = 'jobject'
        elif type(_method.returnType) is AbsApi.BaseType:
499 500 501 502
            if not _method.returnType.name == 'void':
                methodDict['jniUpcallMethod'] = 'CallIntMethod'
                methodDict['jniUpcallType'] = self.translate_type(_method.returnType, jni=True)
                methodDict['isJniUpcallBasicType'] = True
503
        methodDict['returnIfFail'] = '' if  methodDict['return'] == 'void' else ' NULL'
504
        methodDict['hasReturn'] = not methodDict['return'] == 'void'
505 506 507
        methodDict['isSingleListener'] = not _class.multilistener
        methodDict['isMultiListener'] = _class.multilistener

508
        methodDict['firstParam'] = ''
509 510 511 512 513 514 515 516 517 518 519
        methodDict['jobjects'] = []
        methodDict['jenums'] = []
        methodDict['jstrings'] = []
        methodDict['params'] = ''
        methodDict['jparams'] = '('
        methodDict['params_impl'] = ''
        for arg in _method.args:
            argname = self.translate_argument_name(arg.name)
            if arg is not _method.args[0]:
                methodDict['params'] += ', '
                methodDict['params_impl'] += ', '
520 521 522 523 524
            else:
                 methodDict['firstParam'] = argname

            if (arg.type.isconst):
                methodDict['params'] += 'const '
525 526

            if type(arg.type) is AbsApi.ClassType:
527
                methodDict['params'] += 'Linphone' + arg.type.desc.name.to_camel_case() + ' *' + argname
528 529
                methodDict['jparams'] += 'L' + self.jni_path + arg.type.desc.name.to_camel_case() + ';'
                methodDict['params_impl'] += 'j_' + argname
530
                methodDict['jobjects'].append({'objectName': argname, 'className': arg.type.desc.name.to_camel_case(), })
531
            elif type(arg.type) is AbsApi.BaseType:
532
                methodDict['params'] += self.translate_as_c_base_type(arg.type) + ' ' + argname
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
                methodDict['jparams'] += self.translate_java_jni_base_type_name(arg.type.name)
                if arg.type.name == 'string':
                    methodDict['params_impl'] += 'j_' + argname
                    methodDict['jstrings'].append({'stringName': argname,})
                else:
                    methodDict['params_impl'] += argname
            elif type(arg.type) is AbsApi.EnumType:
                methodDict['params'] += 'Linphone' + arg.type.desc.name.to_camel_case() + ' ' + argname
                methodDict['jparams'] += 'L' + self.jni_path + self.translate_java_jni_enum_name(arg.type.desc) + ';'
                methodDict['params_impl'] += 'j_' + argname
                methodDict['jenums'].append({'enumName': argname, 'cEnumPrefix': 'linphone_' + arg.type.desc.name.to_snake_case()})

        methodDict['jparams'] += ')'
        if (methodDict['return'] == 'void'):
            methodDict['jparams'] += 'V'
        else:
549 550 551 552 553 554
            if type(_method.returnType) is AbsApi.ClassType:
                methodDict['jparams'] += 'L' + self.jni_path + _method.returnType.desc.name.to_camel_case() + ';'
            elif type(_method.returnType) is AbsApi.BaseType:
                methodDict['jparams'] += self.translate_java_jni_base_type_name(_method.returnType.name)
            else:
                pass #TODO
555 556 557

        return methodDict

Sylvain Berfini's avatar
Sylvain Berfini committed
558 559
    def translate_interface(self, _class):
        interfaceDict = {
Sylvain Berfini's avatar
Sylvain Berfini committed
560
            'methods': [],
561
            'jniMethods': [],
Sylvain Berfini's avatar
Sylvain Berfini committed
562 563 564 565 566 567
        }

        interfaceDict['doc'] = self.docTranslator.translate(_class.briefDescription)

        for method in _class.methods:
            interfaceDict['methods'].append(self.translate_method(method))
568
            interfaceDict['jniMethods'].append(self.translate_jni_interface(_class.listenedClass, _class.name, method))
Sylvain Berfini's avatar
Sylvain Berfini committed
569 570 571 572

        return interfaceDict

    def translate_enum(self, _class):
573 574 575
        enumDict = {
            'jniMethods': [],
        }
Sylvain Berfini's avatar
Sylvain Berfini committed
576 577 578 579 580 581 582

        enumDict['name'] = _class.name.to_camel_case()
        enumDict['doc'] = self.docTranslator.translate(_class.briefDescription)
        enumDict['values'] = []
        i = 0
        lastValue = None

583 584
        enumDict['jniPath'] = self.jni_path

Sylvain Berfini's avatar
Sylvain Berfini committed
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
        for enumValue in _class.values:
            enumValDict = {}
            enumValDict['name'] = enumValue.name.to_camel_case()
            enumValDict['doc'] = self.docTranslator.translate(enumValue.briefDescription)
            if type(enumValue.value) is int:
                lastValue = enumValue.value
                enumValDict['value'] = str(enumValue.value)
            elif type(enumValue.value) is AbsApi.Flag:
                enumValDict['value'] = '1<<' + str(enumValue.value.position)
            else:
                if lastValue is not None:
                    enumValDict['value'] = lastValue + 1
                    lastValue += 1
                else:
                    enumValDict['value'] = i
            i += 1
            enumValDict['commarorsemicolon'] = ';' if i == len(_class.values) else ','
            enumDict['values'].append(enumValDict)

        return enumDict

##########################################################################

class JavaEnum(object):
609
    def __init__(self, package, _enum, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
610
        self._class = translator.translate_enum(_enum)
611
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
612
        self.className = _enum.name.to_camel_case()
613 614 615 616
        if self.className == 'XmlRpcArgType':
            self.className = 'XmlRpcRequestArgType'
        elif self.className == 'XmlRpcStatus':
            self.className = 'XmlRpcRequestStatus'
617
        self.cPrefix = 'linphone_' + _enum.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
618 619 620
        self.filename = self.className + ".java"
        self.values = self._class['values']
        self.doc = self._class['doc']
621
        self.jniName = translator.translate_java_jni_enum_name(_enum)
622 623 624 625
        if self.className == 'XmlRpcRequestArgType':
            self.jniName = 'XmlRpcRequest$ArgType'
        elif self.className == 'XmlRpcRequestStatus':
            self.jniName = 'XmlRpcRequest$Status'
Sylvain Berfini's avatar
Sylvain Berfini committed
626

627 628 629 630
class JniInterface(object):
    def __init__(self, javaClass, apiClass):
        self.isSingleListener = (not apiClass.multilistener)
        self.isMultiListener = (apiClass.multilistener)
631 632
        self.className = javaClass.className
        self.classCName = javaClass.cName
633 634 635 636
        self.cPrefix = javaClass.cPrefix
        self.callbacks = []
        listener = apiClass.listenerInterface
        for method in listener.methods:
637
            cb = 'linphone_' + listener.name.to_snake_case()[:-9] # Remove _listener at the end
638 639 640 641 642 643
            cbName = cb + '_' + method.name.to_snake_case()
            self.callbacks.append({
                'callbackName': cbName,
                'callback': method.name.to_snake_case()[3:], # Remove the on_
            })

Sylvain Berfini's avatar
Sylvain Berfini committed
644
class JavaInterface(object):
645
    def __init__(self, package, _interface, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
646
        self._class = translator.translate_interface(_interface)
647
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
648 649
        self.className = _interface.name.to_camel_case()
        self.filename = self.className + ".java"
650
        self.cPrefix = 'linphone_' + _interface.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
651 652 653
        self.imports = []
        self.methods = self._class['methods']
        self.doc = self._class['doc']
654
        self.jniMethods = self._class['jniMethods']
Sylvain Berfini's avatar
Sylvain Berfini committed
655 656 657 658 659 660 661 662 663 664

class JavaInterfaceStub(object):
    def __init__(self, _interface):
        self.packageName = _interface.packageName
        self.className = _interface.className
        self.classNameStub =  self.className + "Stub"
        self.filename = self.className + "Stub.java"
        self.methods = _interface.methods

class JavaClass(object):
665
    def __init__(self, package, _class, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
666 667
        self._class = translator.translate_class(_class)
        self.isLinphoneFactory = self._class['isLinphoneFactory']
668
        self.isLinphoneCore = self._class['isLinphoneCore']
669
        self.isNotLinphoneFactory = not self.isLinphoneFactory
Sylvain Berfini's avatar
Sylvain Berfini committed
670 671
        self.cName = 'Linphone' + _class.name.to_camel_case()
        self.cPrefix = 'linphone_' + _class.name.to_snake_case()
672
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
673 674
        self.className = _class.name.to_camel_case()
        self.classImplName = self.className + "Impl"
675
        self.factoryName = _class.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
676 677 678
        self.filename = self.className + ".java"
        self.imports = []
        self.methods = self._class['methods']
679
        self.jniMethods = self._class['jniMethods']
Sylvain Berfini's avatar
Sylvain Berfini committed
680 681
        self.doc = self._class['doc']
        self.enums = []
682 683 684
        self.jniInterface = None
        if _class.listenerInterface is not None:
            self.jniInterface = JniInterface(self, _class)
Sylvain Berfini's avatar
Sylvain Berfini committed
685 686 687 688 689 690 691

    def add_enum(self, enum):
        if enum.className.startswith(self.className):
            enum.className = enum.className[len(self.className):]
        self.enums.append(enum)

class Jni(object):
Sylvain Berfini's avatar
Sylvain Berfini committed
692
    def __init__(self, package):
693
        self.enums = []
694
        self.interfaces = []
695
        self.callbacks = []
696
        self.objects = []
Sylvain Berfini's avatar
Sylvain Berfini committed
697 698 699 700 701 702 703 704
        self.methods = []
        self.jni_package = ''
        self.jni_path = ''
        package_dirs = package.split('.')
        for directory in package_dirs:
            self.jni_package += directory + '_'
            self.jni_path += directory + '/'

705 706 707 708 709 710 711 712 713 714
    def add_enum(self, javaEnum):
        obj = {
            'jniPrefix': self.jni_package,
            'jniPath': self.jni_path,
            'jniName': javaEnum.jniName,
            'cPrefix': javaEnum.cPrefix,
            'className': javaEnum.className,
        }
        self.enums.append(obj)

Sylvain Berfini's avatar
Sylvain Berfini committed
715
    def add_object(self, javaClass):
716 717
        if javaClass.className == 'Factory':
            return
Sylvain Berfini's avatar
Sylvain Berfini committed
718 719 720 721 722 723 724 725 726
        obj = {
            'jniPrefix': self.jni_package,
            'jniPath': self.jni_path,
            'cPrefix': javaClass.cPrefix,
            'className': javaClass.className,
            'classCName': javaClass.cName,
            'classImplName': javaClass.classImplName,
        }
        self.objects.append(obj)
Sylvain Berfini's avatar
Sylvain Berfini committed
727

728 729 730 731 732 733
        jniInterface = javaClass.jniInterface
        if jniInterface is not None:
            interface = {
                'isSingleListener': jniInterface.isSingleListener,
                'isMultiListener': jniInterface.isMultiListener,
                'classCName': jniInterface.classCName,
734
                'className': jniInterface.className,
735 736 737 738 739 740 741
                'cPrefix': jniInterface.cPrefix,
                'jniPackage': self.jni_package,
                'factoryName': javaClass.factoryName,
                'callbacksList': []
            }
            for callback in jniInterface.callbacks:
                interface['callbacksList'].append(callback)
742 743
            self.interfaces.append(interface)

744 745 746 747
    def add_callbacks(self, name, callbacks):
        for callback in callbacks:
            self.callbacks.append(callback)

Sylvain Berfini's avatar
Sylvain Berfini committed
748
    def add_methods(self, name, methods):
Sylvain Berfini's avatar
Sylvain Berfini committed
749 750
        for method in methods:
            self.methods.append(method)
Sylvain Berfini's avatar
Sylvain Berfini committed
751 752 753 754

##########################################################################

class GenWrapper(object):
755
    def __init__(self, srcdir, javadir, package, xmldir, exceptions):
Sylvain Berfini's avatar
Sylvain Berfini committed
756 757
        self.srcdir = srcdir
        self.javadir = javadir
758
        self.package = package
759
        self.exceptions = exceptions
Sylvain Berfini's avatar
Sylvain Berfini committed
760 761 762 763 764

        project = CApi.Project()
        project.initFromDir(xmldir)
        project.check()

765
        self.parser = AbsApi.CParser(project)
766
        self.parser.functionBl = \
767 768 769 770 771
            ['linphone_factory_create_core_with_config',\
            'linphone_factory_create_core',\
            'linphone_factory_create_core_2',\
            'linphone_factory_create_core_with_config_2',\
            'linphone_vcard_get_belcard',\
772 773
            'linphone_core_get_current_vtable',\
            'linphone_factory_get',\
774 775
            'linphone_factory_clean',\
            'linphone_call_zoom_video',\
776
            'linphone_core_get_zrtp_cache_db',\
777
            'linphone_config_get_range']
Sylvain Berfini's avatar
Sylvain Berfini committed
778
        self.parser.parse_all()
779
        self.translator = JavaTranslator(package, exceptions)
Sylvain Berfini's avatar
Sylvain Berfini committed
780
        self.renderer = pystache.Renderer()
Sylvain Berfini's avatar
Sylvain Berfini committed
781
        self.jni = Jni(package)
Sylvain Berfini's avatar
Sylvain Berfini committed
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797

        self.enums = {}
        self.interfaces = {}
        self.classes = {}
        self.enums_to_remove = []

    def render_all(self):
        for _interface in self.parser.interfacesIndex.values():
            self.render_java_interface(_interface)
        for _class in self.parser.classesIndex.values():
            self.render_java_class(_class)
        for _enum in self.parser.enumsIndex.items():
            if _enum[1] is not None:
                self.render_java_enum(_enum[1])

        for name, value in self.enums.iteritems():
798
            self.jni.add_enum(value)
799 800
            if name in ENUMS_LIST:
                className = ENUMS_LIST[name]
Sylvain Berfini's avatar
Sylvain Berfini committed
801 802 803 804 805 806 807 808 809 810 811 812 813
                print 'Enum ' + name + ' belongs to class ' + className
                self.classes[className].add_enum(value)
                self.enums_to_remove.append(name)

        for enum in self.enums_to_remove:
            self.enums.pop(enum, None)

        for name, value in self.enums.iteritems():
            self.render(value, self.javadir + '/' + value.filename)
        for name, value in self.interfaces.iteritems():
            self.render(value, self.javadir + '/' + value.filename)
        for name, value in self.classes.iteritems():
            self.render(value, self.javadir + '/' + value.filename)
Sylvain Berfini's avatar
Sylvain Berfini committed
814
            self.jni.add_object(value)
Sylvain Berfini's avatar
Sylvain Berfini committed
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831

        self.render(self.jni, self.srcdir + '/linphone_jni.cc')

    def render(self, item, path):
        tmppath = path + '.tmp'
        content = ''
        with open(tmppath, mode='w') as f:
            f.write(self.renderer.render(item))
        with open(tmppath, mode='rU') as f:
            content = f.read()
        with open(path, mode='w') as f:
            f.write(content)
        os.unlink(tmppath)

    def render_java_enum(self, _class):
        if _class is not None:
            try:
832
                javaenum = JavaEnum(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
833 834 835 836 837 838 839
                self.enums[javaenum.className] = javaenum
            except AbsApi.Error as e:
                print('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))

    def render_java_interface(self, _class):
        if _class is not None:
            try:
840
                javainterface = JavaInterface(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
841 842
                self.interfaces[javainterface.className] = javainterface
                javaInterfaceStub = JavaInterfaceStub(javainterface)
843
                self.interfaces[javaInterfaceStub.classNameStub] = javaInterfaceStub
Sylvain Berfini's avatar
Sylvain Berfini committed
844 845
            except AbsApi.Error as e:
                print('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
846
            self.jni.add_callbacks(javainterface.className, javainterface.jniMethods)
Sylvain Berfini's avatar
Sylvain Berfini committed
847 848 849 850

    def render_java_class(self, _class):
        if _class is not None:
            try:
851
                javaclass = JavaClass(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
852 853 854 855 856 857 858 859 860 861 862
                self.classes[javaclass.className] = javaclass
            except AbsApi.Error as e:
                print('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
            self.jni.add_methods(javaclass.className, javaclass.jniMethods)

##########################################################################

def main():
    argparser = argparse.ArgumentParser(description='Generate source files for the Java wrapper')
    argparser.add_argument('xmldir', type=str, help='Directory where the XML documentation of the Linphone\'s API generated by Doxygen is placed')
    argparser.add_argument('-o --output', type=str, help='the directory where to generate the source files', dest='outputdir', default='.')
863
    argparser.add_argument('-p --package', type=str, help='the package name for the wrapper', dest='package', default='org.linphone.core')
864
    argparser.add_argument('-n --name', type=str, help='the name of the genarated source file', dest='name', default='linphone_jni.cc')
865
    argparser.add_argument('-e --exceptions', type=bool, help='enable the wrapping of LinphoneStatus into CoreException', dest='exceptions', default=False)
Sylvain Berfini's avatar
Sylvain Berfini committed
866 867 868 869
    args = argparser.parse_args()

    srcdir = args.outputdir + '/src'
    javadir = args.outputdir + '/java'
870 871 872
    package_dirs = args.package.split('.')
    for directory in package_dirs:
        javadir += '/' + directory
Sylvain Berfini's avatar
Sylvain Berfini committed
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887

    try:
        os.makedirs(srcdir)
    except OSError as e:
        if e.errno != errno.EEXIST:
            print("Cannot create '{0}' dircetory: {1}".format(srcdir, e.strerror))
            sys.exit(1)

    try:
        os.makedirs(javadir)
    except OSError as e:
        if e.errno != errno.EEXIST:
            print("Cannot create '{0}' dircetory: {1}".format(javadir, e.strerror))
            sys.exit(1)

888
    genwrapper = GenWrapper(srcdir, javadir, args.package, args.xmldir, args.exceptions)
Sylvain Berfini's avatar
Sylvain Berfini committed
889 890 891 892
    genwrapper.render_all()

if __name__ == '__main__':
    main()