genwrapper.py 38.7 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
    'AudioRoute': 'Call',
46
    'AuthMethod': 'Core',
47 48 49
    'CallDir': 'Call',
    'CallState': 'Call',
    'CallStatus': 'CallLog',
50
    'ChatRoomState': 'ChatRoom',
51
    'ChatMessageDirection': 'ChatMessage',
52 53
    'ChatMessageState': 'ChatMessage',
    'ConfiguringState': 'Core',
54
    'ConsolidatedPresence': 'PresenceModel',
55
    'CoreLogCollectionUploadState': 'Core',
56
    'EcCalibratorStatus': 'Core',
57
    'EventLogType': 'EventLog',
58 59
    'GlobalState': 'Core',
    'FriendListStatus': 'FriendList',
60
    'FriendListSyncStatus': 'FriendList',
61 62
    'IceState': 'CallStats',
    'LimeState': 'Core',
63
    'LogCollectionState': 'Core',
64 65 66
    'MediaDirection': 'Core',
    'MediaEncryption': 'Core',
    'PlayerState': 'Player',
67 68
    'PresenceActivityType': 'PresenceActivity',
    'PublishState': 'Event',
69
    'RegistrationState': 'Core',
70 71
    'StreamType': 'CallStats',
    'SubscriptionDir': 'Event',
72 73
    'SubscribePolicy': 'Friend',
    'TransportType': 'Address',
74
    'TunnelMode': 'Tunnel',
75 76
    'UpnpState': 'Core',
    'VersionUpdateCheckResult': 'Core',
77 78
    'XmlRpcRequestArgType': 'XmlRpcRequest',
    'XmlRpcRequestStatus': 'XmlRpcRequest',
79 80 81 82
}

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

Sylvain Berfini's avatar
Sylvain Berfini committed
83
class JavaTranslator(object):
84 85
    def __init__(self, packageName, exceptions):
        self.exceptions = exceptions
86 87
        package_dirs = packageName.split('.')
        self.jni_package = ''
88
        self.jni_path = ''
89 90
        for directory in package_dirs:
            self.jni_package += directory + '_'
91
            self.jni_path += directory + '/'
92

93
        self.docTranslator = metadoc.JavaDocTranslator()
Sylvain Berfini's avatar
Sylvain Berfini committed
94 95

    def throws_exception(self, _type):
96 97
        if not self.exceptions:
            return False
Sylvain Berfini's avatar
Sylvain Berfini committed
98 99 100 101 102 103 104 105
        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()

106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
    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

138 139
    def translate_as_c_base_type(self, t):
        _type = t.name
140
        if _type == 'string':
141
            return 'char *'
142
        elif _type == 'integer':
143 144 145 146 147 148 149 150 151
            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 + ' *'
152 153
            if t.isconst:
                inttype = 'const ' + inttype
154
            return inttype
155 156 157 158 159 160 161 162 163 164 165
        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':
166
            return 'char **'
167 168 169
        elif _type == 'character':
            return 'char'
        elif _type == 'void':
170 171 172
            if t.isref:
                return 'void *'
            return 'void'
173 174
        return _type

175
    def translate_type(self, _type, native=False, jni=False, isReturn=False):
Sylvain Berfini's avatar
Sylvain Berfini committed
176
        if type(_type) is AbsApi.ListType:
177
            if jni:
178 179 180
                if type(_type.containedTypeDesc) is AbsApi.ClassType:
                    return 'jobjectArray'
                elif type(_type.containedTypeDesc) is AbsApi.BaseType:
181 182
                    if _type.containedTypeDesc.name == 'string':
                        return 'jobjectArray'
183 184 185
                    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
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
            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:
201
            if jni:
202
                return 'jobject'
Sylvain Berfini's avatar
Sylvain Berfini committed
203 204 205 206
            return _type.desc.name.to_camel_case()
        elif type(_type) is AbsApi.EnumType:
            if native:
                return 'int'
207 208
            elif jni:
                return 'jint'
209 210
            if _type.desc.name.to_camel_case() == "XmlRpcStatus":
                return "XmlRpcRequest.Status"
211 212
            elif _type.desc.name.to_camel_case() == "XmlRpcArgType":
                return "XmlRpcRequest.ArgType"
213 214 215 216
            name = _type.desc.name.to_camel_case()
            if name in ENUMS_LIST:
                className = ENUMS_LIST[name]
                if name.startswith(className):
217 218
                    name = name[len(className):]
                name = className + '.' + name
219
            return name
Sylvain Berfini's avatar
Sylvain Berfini committed
220 221
        elif type(_type) is AbsApi.BaseType:
            if _type.name == 'string':
222 223
                if jni:
                    return 'jstring'
Sylvain Berfini's avatar
Sylvain Berfini committed
224 225
                return 'String'
            elif _type.name == 'integer':
226 227 228 229
                if _type.size is not None and _type.isref:
                    if jni:
                        return 'jbyteArray'
                    return 'byte[]'
230 231
                if jni:
                    return 'jint'
Sylvain Berfini's avatar
Sylvain Berfini committed
232
                return 'int'
233 234 235 236
            elif _type.name == 'boolean':
                if jni:
                    return 'jboolean'
                return 'boolean'
Sylvain Berfini's avatar
Sylvain Berfini committed
237
            elif _type.name == 'floatant':
238 239
                if jni:
                    return 'jfloat'
Sylvain Berfini's avatar
Sylvain Berfini committed
240 241
                return 'float'
            elif _type.name == 'size':
242 243
                if jni:
                    return 'jint'
Sylvain Berfini's avatar
Sylvain Berfini committed
244
                return 'int'
245
            elif _type.name == 'time':
246 247
                if jni:
                    return 'jlong'
248
                return 'long'
249
            elif _type.name == 'status':
250 251
                if jni:
                    return 'jint'
252 253 254
                if native:
                    return 'int'
                return 'void'
255 256 257 258
            elif _type.name == 'string_array':
                if jni:
                    return 'jobjectArray'
                return 'String[]'
259 260 261 262
            elif _type.name == 'character':
                if jni:
                    return 'jchar'
                return 'char'
263
            elif _type.name == 'void':
264 265
                if isReturn:
                    return 'void'
266
                if jni:
267
                    return 'jobject'
268
                return 'Object'
Sylvain Berfini's avatar
Sylvain Berfini committed
269 270
            return _type.name

271 272
    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
273 274 275 276 277 278 279 280 281

    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

282
    def translate_jni_property(self, className, _property):
283 284
        properties = []
        if _property.getter is not None:
285
            properties.append(self.translate_jni_method(className, _property.getter))
286
        if _property.setter is not None:
287
            properties.append(self.translate_jni_method(className, _property.setter))
288 289
        return properties

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
    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
320 321 322
    def translate_method(self, _method):
        methodDict = {}

323 324
        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
325
        methodDict['return_keyword'] = '' if methodDict['return'] == 'void' else 'return '
326
        methodDict['hasReturn'] = not methodDict['return'] == 'void'
Sylvain Berfini's avatar
Sylvain Berfini committed
327 328 329 330 331 332

        methodDict['convertInputClassArrayToLongArray'] = False

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

333
        methodDict['enumCast'] = type(_method.returnType) is AbsApi.EnumType
334
        methodDict['classCast'] = type(_method.returnType) is AbsApi.ClassType
335

Sylvain Berfini's avatar
Sylvain Berfini committed
336 337 338 339 340 341 342
        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'] += ', '
343
                methodDict['static_native_params'] += ', '
Sylvain Berfini's avatar
Sylvain Berfini committed
344 345 346 347 348 349
            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)
350
            if type(arg.type) is AbsApi.EnumType:
351
                methodDict['native_params_impl'] += self.translate_argument_name(arg.name) + '.toInt()'
Sylvain Berfini's avatar
Sylvain Berfini committed
352 353 354 355
            else:
                methodDict['native_params_impl'] += self.translate_argument_name(arg.name)

        methodDict['deprecated'] = _method.deprecated
356
        methodDict['doc'] = _method.briefDescription.translate(self.docTranslator) if _method.briefDescription is not None else None
Sylvain Berfini's avatar
Sylvain Berfini committed
357 358 359

        return methodDict

360
    def translate_jni_method(self, className, _method, static=False):
361
        methodDict = {}
362 363
        methodDict['classCName'] = 'Linphone' + className.to_camel_case()
        methodDict['className'] = className.to_camel_case()
364 365
        methodDict['classImplName'] = className.to_camel_case() + 'Impl'
        methodDict['jniPath'] = self.jni_path
366 367

        methodDict['return'] = self.translate_type(_method.returnType, jni=True, isReturn=True)
368
        methodDict['hasListReturn'] = methodDict['return'] == 'jobjectArray'
369 370
        methodDict['hasByteArrayReturn'] = methodDict['return'] == 'jbyteArray'
        methodDict['hasReturn'] = not methodDict['return'] == 'void' and not methodDict['hasListReturn'] and not methodDict['hasByteArrayReturn']
371
        methodDict['hasStringReturn'] = methodDict['return'] == 'jstring'
372
        methodDict['hasNormalReturn'] = not methodDict['hasListReturn'] and not methodDict['hasStringReturn'] and not methodDict['hasByteArrayReturn']
373 374 375 376 377
        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)
378 379
        methodDict['isRealObjectArray'] = False
        methodDict['isStringObjectArray'] = False
380
        methodDict['c_type_return'] = self.translate_as_c_base_type(_method.returnType)
381 382

        if methodDict['hasListReturn']:
383 384 385
            if type(_method.returnType) is AbsApi.BaseType and _method.returnType.name == 'string_array':
                methodDict['isStringObjectArray'] = True
            elif type(_method.returnType.containedTypeDesc) is AbsApi.BaseType:
386 387 388
                methodDict['isStringObjectArray'] = True
            elif type(_method.returnType.containedTypeDesc) is AbsApi.ClassType:
                methodDict['isRealObjectArray'] = True
389
                methodDict['objectCPrefix'] = 'linphone_' + _method.returnType.containedTypeDesc.desc.name.to_snake_case()
390 391 392
                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'
393

394
        methodDict['params'] = 'JNIEnv *env, jobject thiz, jlong ptr'
395 396 397
        methodDict['params_impl'] = ''
        methodDict['strings'] = []
        methodDict['objects'] = []
398 399
        methodDict['lists'] = []
        methodDict['array'] = []
400
        methodDict['bytes'] = []
401
        methodDict['returnedObjectGetter'] = ''
402
        for arg in _method.args:
403
            methodDict['params'] += ', '
404 405 406 407 408 409
            if static:
                if arg is not _method.args[0]:
                    methodDict['params_impl'] += ', '
            else:
                methodDict['params_impl'] += ', '

410
            methodDict['params'] += self.translate_argument(arg, jni=True)
411 412 413
            argname = self.translate_argument_name(arg.name)

            if type(arg.type) is AbsApi.ClassType:
414 415 416 417
                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})
418 419
                methodDict['params_impl'] += 'c_' + argname
                
420 421 422
            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
423
                methodDict['lists'].append({'list': argname, 'isStringList': isStringList, 'isObjList': isObjList, 'objectClassCName': arg.type.containedTypeDesc.name})
424
                methodDict['params_impl'] += 'bctbx_list_' + argname
425 426 427 428

            elif type(arg.type) is AbsApi.EnumType:
                argCType = arg.type.name
                methodDict['params_impl'] += '(' + argCType + ') ' + argname
429
                
430
            elif type(arg.type) is AbsApi.BaseType:
431
                if arg.type.name == 'integer' and arg.type.size is not None and arg.type.isref:
432 433 434
                    methodDict['bytes'].append({'bytesargname': argname, 'bytesargtype' : self.translate_as_c_base_type(arg.type)})
                    methodDict['params_impl'] += 'c_' + argname
                elif arg.type.name == 'string':
435 436 437
                    methodDict['strings'].append({'string': argname})
                    methodDict['params_impl'] += 'c_' + argname
                else:
438
                    methodDict['params_impl'] += '(' + self.translate_as_c_base_type(arg.type) + ')' + argname                        
439 440
            else:
                methodDict['params_impl'] += argname
441 442 443

        return methodDict

Sylvain Berfini's avatar
Sylvain Berfini committed
444 445
    def translate_class(self, _class):
        classDict = {
446 447
            'methods': [],
            'jniMethods': [],
Sylvain Berfini's avatar
Sylvain Berfini committed
448 449 450
        }

        classDict['isLinphoneFactory'] = _class.name.to_camel_case() == "Factory"
451
        classDict['isLinphoneCore'] = _class.name.to_camel_case() == "Core"
452
        classDict['doc'] = _class.briefDescription.translate(self.docTranslator) if _class.briefDescription is not None else None
Sylvain Berfini's avatar
Sylvain Berfini committed
453 454 455 456

        for _property in _class.properties:
            try:
                classDict['methods'] += self.translate_property(_property)
457
                classDict['jniMethods'] += self.translate_jni_property(_class.name, _property)
Sylvain Berfini's avatar
Sylvain Berfini committed
458 459 460 461 462 463
            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)
464
                jniMethodDict = self.translate_jni_method(_class.name, method)
Sylvain Berfini's avatar
Sylvain Berfini committed
465
                classDict['methods'].append(methodDict)
466
                classDict['jniMethods'].append(jniMethodDict)
Sylvain Berfini's avatar
Sylvain Berfini committed
467 468 469 470 471 472
            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)
473
                jniMethodDict = self.translate_jni_method(_class.name, method, True)
474
                classDict['methods'].append(methodDict)
475
                classDict['jniMethods'].append(jniMethodDict)
Sylvain Berfini's avatar
Sylvain Berfini committed
476 477 478
            except AbsApi.Error as e:
                print('Could not translate {0}: {1}'.format(method.name.to_snake_case(fullName=True), e.args[0]))

479 480 481 482 483 484 485 486 487
        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
488 489
        return classDict

490
    def translate_jni_interface(self, _class, className, _method):
491 492 493 494 495 496
        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
497
        methodDict['cPrefix'] = 'linphone_' + className.to_snake_case()[:-9] # Remove _listener at the end
498 499
        methodDict['callbackName'] = methodDict['cPrefix'] + '_' + _method.name.to_snake_case()
        methodDict['jname'] = _method.name.to_camel_case(lower=True)
500
        methodDict['return'] = self.translate_as_c_base_type(_method.returnType)
501 502 503
        methodDict['jniUpcallMethod'] = 'CallVoidMethod'
        methodDict['isJniUpcallBasicType'] = False
        methodDict['isJniUpcallObject'] = False
504 505
        if type(_method.returnType) is AbsApi.ClassType:
            methodDict['return'] += '*'
506 507 508 509
            methodDict['jniUpcallMethod'] = 'CallObjectMethod'
            methodDict['isJniUpcallObject'] = True
            methodDict['jniUpcallType'] = 'jobject'
        elif type(_method.returnType) is AbsApi.BaseType:
510 511 512 513
            if not _method.returnType.name == 'void':
                methodDict['jniUpcallMethod'] = 'CallIntMethod'
                methodDict['jniUpcallType'] = self.translate_type(_method.returnType, jni=True)
                methodDict['isJniUpcallBasicType'] = True
514
        methodDict['returnIfFail'] = '' if  methodDict['return'] == 'void' else ' NULL'
515
        methodDict['hasReturn'] = not methodDict['return'] == 'void'
516 517 518
        methodDict['isSingleListener'] = not _class.multilistener
        methodDict['isMultiListener'] = _class.multilistener

519
        methodDict['firstParam'] = ''
520 521 522 523 524 525 526 527 528 529 530
        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'] += ', '
531 532 533 534 535
            else:
                 methodDict['firstParam'] = argname

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

            if type(arg.type) is AbsApi.ClassType:
538
                methodDict['params'] += 'Linphone' + arg.type.desc.name.to_camel_case() + ' *' + argname
539 540
                methodDict['jparams'] += 'L' + self.jni_path + arg.type.desc.name.to_camel_case() + ';'
                methodDict['params_impl'] += 'j_' + argname
541
                methodDict['jobjects'].append({'objectName': argname, 'className': arg.type.desc.name.to_camel_case(), })
542
            elif type(arg.type) is AbsApi.BaseType:
543
                methodDict['params'] += self.translate_as_c_base_type(arg.type) + ' ' + argname
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
                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:
560 561 562 563 564 565
            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
566 567 568

        return methodDict

Sylvain Berfini's avatar
Sylvain Berfini committed
569 570
    def translate_interface(self, _class):
        interfaceDict = {
571
            'methods': [],
572
            'jniMethods': [],
Sylvain Berfini's avatar
Sylvain Berfini committed
573 574
        }

575
        interfaceDict['doc'] = _class.briefDescription.translate(self.docTranslator)
Sylvain Berfini's avatar
Sylvain Berfini committed
576 577 578

        for method in _class.methods:
            interfaceDict['methods'].append(self.translate_method(method))
579
            interfaceDict['jniMethods'].append(self.translate_jni_interface(_class.listenedClass, _class.name, method))
Sylvain Berfini's avatar
Sylvain Berfini committed
580 581 582

        return interfaceDict

583
    def translate_enum(self, enum):
584 585 586
        enumDict = {
            'jniMethods': [],
        }
Sylvain Berfini's avatar
Sylvain Berfini committed
587

588 589
        enumDict['name'] = enum.name.to_camel_case()
        enumDict['doc'] = enum.briefDescription.translate(self.docTranslator)
Sylvain Berfini's avatar
Sylvain Berfini committed
590 591 592 593
        enumDict['values'] = []
        i = 0
        lastValue = None

594 595
        enumDict['jniPath'] = self.jni_path

596
        for enumValue in enum.enumerators:
Sylvain Berfini's avatar
Sylvain Berfini committed
597 598
            enumValDict = {}
            enumValDict['name'] = enumValue.name.to_camel_case()
599
            enumValDict['doc'] = enumValue.briefDescription.translate(self.docTranslator)
Sylvain Berfini's avatar
Sylvain Berfini committed
600 601 602 603 604 605 606 607 608 609 610 611
            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
612
            enumValDict['commarorsemicolon'] = ';' if i == len(enum.enumerators) else ','
Sylvain Berfini's avatar
Sylvain Berfini committed
613 614 615 616 617 618 619
            enumDict['values'].append(enumValDict)

        return enumDict

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

class JavaEnum(object):
620
    def __init__(self, package, _enum, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
621
        self._class = translator.translate_enum(_enum)
622
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
623
        self.className = _enum.name.to_camel_case()
624 625 626 627
        if self.className == 'XmlRpcArgType':
            self.className = 'XmlRpcRequestArgType'
        elif self.className == 'XmlRpcStatus':
            self.className = 'XmlRpcRequestStatus'
628
        self.cPrefix = 'linphone_' + _enum.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
629 630 631
        self.filename = self.className + ".java"
        self.values = self._class['values']
        self.doc = self._class['doc']
632
        self.jniName = translator.translate_java_jni_enum_name(_enum)
633 634 635 636
        if self.className == 'XmlRpcRequestArgType':
            self.jniName = 'XmlRpcRequest$ArgType'
        elif self.className == 'XmlRpcRequestStatus':
            self.jniName = 'XmlRpcRequest$Status'
Sylvain Berfini's avatar
Sylvain Berfini committed
637

638 639 640 641
class JniInterface(object):
    def __init__(self, javaClass, apiClass):
        self.isSingleListener = (not apiClass.multilistener)
        self.isMultiListener = (apiClass.multilistener)
642 643
        self.className = javaClass.className
        self.classCName = javaClass.cName
644 645 646 647
        self.cPrefix = javaClass.cPrefix
        self.callbacks = []
        listener = apiClass.listenerInterface
        for method in listener.methods:
648
            cb = 'linphone_' + listener.name.to_snake_case()[:-9] # Remove _listener at the end
649 650 651 652 653 654
            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
655
class JavaInterface(object):
656
    def __init__(self, package, _interface, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
657
        self._class = translator.translate_interface(_interface)
658
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
659 660
        self.className = _interface.name.to_camel_case()
        self.filename = self.className + ".java"
661
        self.cPrefix = 'linphone_' + _interface.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
662 663 664
        self.imports = []
        self.methods = self._class['methods']
        self.doc = self._class['doc']
665
        self.jniMethods = self._class['jniMethods']
Sylvain Berfini's avatar
Sylvain Berfini committed
666 667 668 669 670 671 672 673 674 675

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):
676
    def __init__(self, package, _class, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
677 678
        self._class = translator.translate_class(_class)
        self.isLinphoneFactory = self._class['isLinphoneFactory']
679
        self.isLinphoneCore = self._class['isLinphoneCore']
680
        self.isNotLinphoneFactory = not self.isLinphoneFactory
681 682
        self.cName = 'Linphone' + _class.name.to_camel_case()
        self.cPrefix = 'linphone_' + _class.name.to_snake_case()
683
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
684 685
        self.className = _class.name.to_camel_case()
        self.classImplName = self.className + "Impl"
686
        self.factoryName = _class.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
687 688 689
        self.filename = self.className + ".java"
        self.imports = []
        self.methods = self._class['methods']
690
        self.jniMethods = self._class['jniMethods']
Sylvain Berfini's avatar
Sylvain Berfini committed
691 692
        self.doc = self._class['doc']
        self.enums = []
693 694 695
        self.jniInterface = None
        if _class.listenerInterface is not None:
            self.jniInterface = JniInterface(self, _class)
Sylvain Berfini's avatar
Sylvain Berfini committed
696 697 698 699 700 701 702

    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):
703
    def __init__(self, package):
704
        self.enums = []
705
        self.interfaces = []
706
        self.callbacks = []
707
        self.objects = []
708 709 710 711 712 713 714 715
        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 + '/'

716 717 718 719 720 721 722 723 724 725
    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)

726
    def add_object(self, javaClass):
727 728
        if javaClass.className == 'Factory':
            return
729 730 731 732 733 734 735 736 737
        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
738

739 740 741 742 743 744
        jniInterface = javaClass.jniInterface
        if jniInterface is not None:
            interface = {
                'isSingleListener': jniInterface.isSingleListener,
                'isMultiListener': jniInterface.isMultiListener,
                'classCName': jniInterface.classCName,
745
                'className': jniInterface.className,
746 747 748 749 750 751 752
                'cPrefix': jniInterface.cPrefix,
                'jniPackage': self.jni_package,
                'factoryName': javaClass.factoryName,
                'callbacksList': []
            }
            for callback in jniInterface.callbacks:
                interface['callbacksList'].append(callback)
753 754
            self.interfaces.append(interface)

755 756 757 758
    def add_callbacks(self, name, callbacks):
        for callback in callbacks:
            self.callbacks.append(callback)

Sylvain Berfini's avatar
Sylvain Berfini committed
759
    def add_methods(self, name, methods):
760 761
        for method in methods:
            self.methods.append(method)
Sylvain Berfini's avatar
Sylvain Berfini committed
762

763 764 765 766 767 768 769 770 771 772 773 774 775
class Proguard(object):
    def __init__(self, package):
        self.package = package
        self.classes = []

    def add_class(self, javaClass):
        obj = {
            'package': self.package,
            'className': javaClass.className,
            'classImplName': javaClass.classImplName,
        }
        self.classes.append(obj)

Sylvain Berfini's avatar
Sylvain Berfini committed
776 777 778
##########################################################################

class GenWrapper(object):
779
    def __init__(self, srcdir, javadir, package, xmldir, exceptions):
Sylvain Berfini's avatar
Sylvain Berfini committed
780 781
        self.srcdir = srcdir
        self.javadir = javadir
782
        self.package = package
783
        self.exceptions = exceptions
Sylvain Berfini's avatar
Sylvain Berfini committed
784 785 786 787 788

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

789
        self.parser = AbsApi.CParser(project)
790
        self.parser.functionBl = \
791 792 793 794 795
            ['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',\
796 797
            'linphone_core_get_current_vtable',\
            'linphone_factory_get',\
798 799
            'linphone_factory_clean',\
            'linphone_call_zoom_video',\
800
            'linphone_core_get_zrtp_cache_db',\
801
            'linphone_config_get_range']
Sylvain Berfini's avatar
Sylvain Berfini committed
802
        self.parser.parse_all()
803
        self.translator = JavaTranslator(package, exceptions)
Sylvain Berfini's avatar
Sylvain Berfini committed
804
        self.renderer = pystache.Renderer()
805
        self.jni = Jni(package)
806
        self.proguard = Proguard(package)
Sylvain Berfini's avatar
Sylvain Berfini committed
807 808 809 810 811 812 813 814 815 816 817 818 819 820 821

        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])

822
        for name, value in self.enums.items():
823
            self.jni.add_enum(value)
824 825
            if name in ENUMS_LIST:
                className = ENUMS_LIST[name]
826
                print('Enum ' + name + ' belongs to class ' + className)
Sylvain Berfini's avatar
Sylvain Berfini committed
827 828 829 830 831 832
                self.classes[className].add_enum(value)
                self.enums_to_remove.append(name)

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

833
        for name, value in self.enums.items():
Sylvain Berfini's avatar
Sylvain Berfini committed
834
            self.render(value, self.javadir + '/' + value.filename)
835
        for name, value in self.interfaces.items():
Sylvain Berfini's avatar
Sylvain Berfini committed
836
            self.render(value, self.javadir + '/' + value.filename)
837
        for name, value in self.classes.items():
Sylvain Berfini's avatar
Sylvain Berfini committed
838
            self.render(value, self.javadir + '/' + value.filename)
839
            self.jni.add_object(value)
840
            self.proguard.add_class(value)
Sylvain Berfini's avatar
Sylvain Berfini committed
841 842

        self.render(self.jni, self.srcdir + '/linphone_jni.cc')
843
        self.render(self.proguard, self.srcdir + '/proguard.txt')
Sylvain Berfini's avatar
Sylvain Berfini committed
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858

    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:
859
                javaenum = JavaEnum(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
860 861 862 863 864 865 866
                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:
867
                javainterface = JavaInterface(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
868 869
                self.interfaces[javainterface.className] = javainterface
                javaInterfaceStub = JavaInterfaceStub(javainterface)
870
                self.interfaces[javaInterfaceStub.classNameStub] = javaInterfaceStub
Sylvain Berfini's avatar
Sylvain Berfini committed
871 872
            except AbsApi.Error as e:
                print('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
873
            self.jni.add_callbacks(javainterface.className, javainterface.jniMethods)
Sylvain Berfini's avatar
Sylvain Berfini committed
874 875 876 877

    def render_java_class(self, _class):
        if _class is not None:
            try:
878
                javaclass = JavaClass(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
879 880 881 882 883 884 885 886 887 888 889
                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='.')
890
    argparser.add_argument('-p --package', type=str, help='the package name for the wrapper', dest='package', default='org.linphone.core')
891
    argparser.add_argument('-n --name', type=str, help='the name of the genarated source file', dest='name', default='linphone_jni.cc')
892
    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
893 894 895 896
    args = argparser.parse_args()

    srcdir = args.outputdir + '/src'
    javadir = args.outputdir + '/java'
897 898 899
    package_dirs = args.package.split('.')
    for directory in package_dirs:
        javadir += '/' + directory
Sylvain Berfini's avatar
Sylvain Berfini committed
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914

    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)

915
    genwrapper = GenWrapper(srcdir, javadir, args.package, args.xmldir, args.exceptions)
Sylvain Berfini's avatar
Sylvain Berfini committed
916 917 918 919
    genwrapper.render_all()

if __name__ == '__main__':
    main()