genwrapper.py 38.2 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 49 50 51
    'CallDir': 'Call',
    'CallState': 'Call',
    'CallStatus': 'CallLog',
    'ChatMessageState': 'ChatMessage',
    'ConfiguringState': 'Core',
    'CoreLogCollectionUploadState': 'Core',
52
    'EcCalibratorStatus': 'Core',
53 54 55 56 57 58 59 60 61 62
    'GlobalState': 'Core',
    'FriendListStatus': 'FriendList',
    'IceState': 'CallStats',
    'LimeState': 'Core',
    'MediaDirection': 'Core',
    'MediaEncryption': 'Core',
    'PlayerState': 'Player',
    'RegistrationState': 'Core',
    'SubscribePolicy': 'Friend',
    'TransportType': 'Address',
63
    'TunnelMode': 'Tunnel',
64 65
    'XmlRpcRequestArgType': 'XmlRpcRequest',
    'XmlRpcRequestStatus': 'XmlRpcRequest',
66 67 68 69
}

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

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

Sylvain Berfini's avatar
Sylvain Berfini committed
80 81 82
        self.docTranslator = metadoc.SandcastleJavaTranslator()

    def throws_exception(self, _type):
83 84
        if not self.exceptions:
            return False
Sylvain Berfini's avatar
Sylvain Berfini committed
85 86 87 88 89 90 91 92
        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()

93 94 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
    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

125 126
    def translate_as_c_base_type(self, t):
        _type = t.name
127
        if _type == 'string':
128
            return 'char *'
129
        elif _type == 'integer':
130 131 132 133 134 135 136 137 138
            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 + ' *'
139 140
            if t.isconst:
                inttype = 'const ' + inttype
141
            return inttype
142 143 144 145 146 147 148 149 150 151 152
        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':
153
            return 'char **'
154 155 156
        elif _type == 'character':
            return 'char'
        elif _type == 'void':
157 158 159
            if t.isref:
                return 'void *'
            return 'void'
160 161
        return _type

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

266 267
    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
268 269 270 271 272 273 274 275 276

    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
277
    def translate_jni_property(self, className, _property):
278 279
        properties = []
        if _property.getter is not None:
Sylvain Berfini's avatar
Sylvain Berfini committed
280
            properties.append(self.translate_jni_method(className, _property.getter))
281
        if _property.setter is not None:
Sylvain Berfini's avatar
Sylvain Berfini committed
282
            properties.append(self.translate_jni_method(className, _property.setter))
283 284
        return properties

285 286 287 288 289 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
    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
315 316 317
    def translate_method(self, _method):
        methodDict = {}

Sylvain Berfini's avatar
Sylvain Berfini committed
318 319
        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
320
        methodDict['return_keyword'] = '' if methodDict['return'] == 'void' else 'return '
321
        methodDict['hasReturn'] = not methodDict['return'] == 'void'
Sylvain Berfini's avatar
Sylvain Berfini committed
322 323 324 325 326 327

        methodDict['convertInputClassArrayToLongArray'] = False

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

328
        methodDict['enumCast'] = type(_method.returnType) is AbsApi.EnumType
329
        methodDict['classCast'] = type(_method.returnType) is AbsApi.ClassType
330

Sylvain Berfini's avatar
Sylvain Berfini committed
331 332 333 334 335 336 337
        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'] += ', '
338
                methodDict['static_native_params'] += ', '
Sylvain Berfini's avatar
Sylvain Berfini committed
339 340 341 342 343 344
            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
345
            if type(arg.type) is AbsApi.EnumType:
346
                methodDict['native_params_impl'] += self.translate_argument_name(arg.name) + '.toInt()'
Sylvain Berfini's avatar
Sylvain Berfini committed
347 348 349 350 351 352 353 354
            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
355
    def translate_jni_method(self, className, _method, static=False):
356
        methodDict = {}
Sylvain Berfini's avatar
Sylvain Berfini committed
357 358
        methodDict['classCName'] = 'Linphone' + className.to_camel_case()
        methodDict['className'] = className.to_camel_case()
359 360
        methodDict['classImplName'] = className.to_camel_case() + 'Impl'
        methodDict['jniPath'] = self.jni_path
Sylvain Berfini's avatar
Sylvain Berfini committed
361 362

        methodDict['return'] = self.translate_type(_method.returnType, jni=True, isReturn=True)
363
        methodDict['hasListReturn'] = methodDict['return'] == 'jobjectArray'
364 365
        methodDict['hasByteArrayReturn'] = methodDict['return'] == 'jbyteArray'
        methodDict['hasReturn'] = not methodDict['return'] == 'void' and not methodDict['hasListReturn'] and not methodDict['hasByteArrayReturn']
366
        methodDict['hasStringReturn'] = methodDict['return'] == 'jstring'
367
        methodDict['hasNormalReturn'] = not methodDict['hasListReturn'] and not methodDict['hasStringReturn'] and not methodDict['hasByteArrayReturn']
Sylvain Berfini's avatar
Sylvain Berfini committed
368 369 370 371 372
        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)
373 374
        methodDict['isRealObjectArray'] = False
        methodDict['isStringObjectArray'] = False
375
        methodDict['c_type_return'] = self.translate_as_c_base_type(_method.returnType)
376 377

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

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

405
            methodDict['params'] += self.translate_argument(arg, jni=True)
Sylvain Berfini's avatar
Sylvain Berfini committed
406 407 408
            argname = self.translate_argument_name(arg.name)

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

            elif type(arg.type) is AbsApi.EnumType:
                argCType = arg.type.name
                methodDict['params_impl'] += '(' + argCType + ') ' + argname
424
                
Sylvain Berfini's avatar
Sylvain Berfini committed
425
            elif type(arg.type) is AbsApi.BaseType:
426
                if (arg.type.name == 'integer' and arg.type.size is not None and arg.type.isref) or (arg.type.name == 'void' and arg.type.isref and arg.type.isconst):
427 428 429
                    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
430 431 432
                    methodDict['strings'].append({'string': argname})
                    methodDict['params_impl'] += 'c_' + argname
                else:
433
                    methodDict['params_impl'] += '(' + self.translate_as_c_base_type(arg.type) + ')' + argname                        
Sylvain Berfini's avatar
Sylvain Berfini committed
434 435
            else:
                methodDict['params_impl'] += argname
436 437 438

        return methodDict

Sylvain Berfini's avatar
Sylvain Berfini committed
439 440
    def translate_class(self, _class):
        classDict = {
Sylvain Berfini's avatar
Sylvain Berfini committed
441 442
            'methods': [],
            'jniMethods': [],
Sylvain Berfini's avatar
Sylvain Berfini committed
443 444 445
        }

        classDict['isLinphoneFactory'] = _class.name.to_camel_case() == "Factory"
446
        classDict['isLinphoneCore'] = _class.name.to_camel_case() == "Core"
Sylvain Berfini's avatar
Sylvain Berfini committed
447 448 449 450 451
        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
452
                classDict['jniMethods'] += self.translate_jni_property(_class.name, _property)
Sylvain Berfini's avatar
Sylvain Berfini committed
453 454 455 456 457 458
            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
459
                jniMethodDict = self.translate_jni_method(_class.name, method)
Sylvain Berfini's avatar
Sylvain Berfini committed
460
                classDict['methods'].append(methodDict)
461
                classDict['jniMethods'].append(jniMethodDict)
Sylvain Berfini's avatar
Sylvain Berfini committed
462 463 464 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]))

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

474 475 476 477 478 479 480 481 482
        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
483 484
        return classDict

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

514
        methodDict['firstParam'] = ''
515 516 517 518 519 520 521 522 523 524 525
        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'] += ', '
526 527 528 529 530
            else:
                 methodDict['firstParam'] = argname

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

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

        return methodDict

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

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

        for method in _class.methods:
            interfaceDict['methods'].append(self.translate_method(method))
574
            interfaceDict['jniMethods'].append(self.translate_jni_interface(_class.listenedClass, _class.name, method))
Sylvain Berfini's avatar
Sylvain Berfini committed
575 576 577 578

        return interfaceDict

    def translate_enum(self, _class):
579 580 581
        enumDict = {
            'jniMethods': [],
        }
Sylvain Berfini's avatar
Sylvain Berfini committed
582 583 584 585 586 587 588

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

589 590
        enumDict['jniPath'] = self.jni_path

Sylvain Berfini's avatar
Sylvain Berfini committed
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
        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):
615
    def __init__(self, package, _enum, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
616
        self._class = translator.translate_enum(_enum)
617
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
618
        self.className = _enum.name.to_camel_case()
619 620 621 622
        if self.className == 'XmlRpcArgType':
            self.className = 'XmlRpcRequestArgType'
        elif self.className == 'XmlRpcStatus':
            self.className = 'XmlRpcRequestStatus'
623
        self.cPrefix = 'linphone_' + _enum.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
624 625 626
        self.filename = self.className + ".java"
        self.values = self._class['values']
        self.doc = self._class['doc']
627
        self.jniName = translator.translate_java_jni_enum_name(_enum)
628 629 630 631
        if self.className == 'XmlRpcRequestArgType':
            self.jniName = 'XmlRpcRequest$ArgType'
        elif self.className == 'XmlRpcRequestStatus':
            self.jniName = 'XmlRpcRequest$Status'
Sylvain Berfini's avatar
Sylvain Berfini committed
632

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

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

    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
698
    def __init__(self, package):
699
        self.enums = []
700
        self.interfaces = []
701
        self.callbacks = []
702
        self.objects = []
Sylvain Berfini's avatar
Sylvain Berfini committed
703 704 705 706 707 708 709 710
        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 + '/'

711 712 713 714 715 716 717 718 719 720
    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
721
    def add_object(self, javaClass):
722 723
        if javaClass.className == 'Factory':
            return
Sylvain Berfini's avatar
Sylvain Berfini committed
724 725 726 727 728 729 730 731 732
        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
733

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

750 751 752 753
    def add_callbacks(self, name, callbacks):
        for callback in callbacks:
            self.callbacks.append(callback)

Sylvain Berfini's avatar
Sylvain Berfini committed
754
    def add_methods(self, name, methods):
Sylvain Berfini's avatar
Sylvain Berfini committed
755 756
        for method in methods:
            self.methods.append(method)
Sylvain Berfini's avatar
Sylvain Berfini committed
757 758 759 760

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

class GenWrapper(object):
761
    def __init__(self, srcdir, javadir, package, xmldir, exceptions):
Sylvain Berfini's avatar
Sylvain Berfini committed
762 763
        self.srcdir = srcdir
        self.javadir = javadir
764
        self.package = package
765
        self.exceptions = exceptions
Sylvain Berfini's avatar
Sylvain Berfini committed
766 767 768 769 770

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

771
        self.parser = AbsApi.CParser(project)
772
        self.parser.functionBl = \
773 774 775 776 777
            ['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',\
778 779
            'linphone_core_get_current_vtable',\
            'linphone_factory_get',\
780 781
            'linphone_factory_clean',\
            'linphone_call_zoom_video',\
782
            'linphone_core_get_zrtp_cache_db',\
783
            'linphone_config_get_range']
Sylvain Berfini's avatar
Sylvain Berfini committed
784
        self.parser.parse_all()
785
        self.translator = JavaTranslator(package, exceptions)
Sylvain Berfini's avatar
Sylvain Berfini committed
786
        self.renderer = pystache.Renderer()
Sylvain Berfini's avatar
Sylvain Berfini committed
787
        self.jni = Jni(package)
Sylvain Berfini's avatar
Sylvain Berfini committed
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803

        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():
804
            self.jni.add_enum(value)
805 806
            if name in ENUMS_LIST:
                className = ENUMS_LIST[name]
Sylvain Berfini's avatar
Sylvain Berfini committed
807 808 809 810 811 812 813 814 815 816 817 818 819
                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
820
            self.jni.add_object(value)
Sylvain Berfini's avatar
Sylvain Berfini committed
821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837

        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:
838
                javaenum = JavaEnum(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
839 840 841 842 843 844 845
                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:
846
                javainterface = JavaInterface(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
847 848
                self.interfaces[javainterface.className] = javainterface
                javaInterfaceStub = JavaInterfaceStub(javainterface)
849
                self.interfaces[javaInterfaceStub.classNameStub] = javaInterfaceStub
Sylvain Berfini's avatar
Sylvain Berfini committed
850 851
            except AbsApi.Error as e:
                print('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
852
            self.jni.add_callbacks(javainterface.className, javainterface.jniMethods)
Sylvain Berfini's avatar
Sylvain Berfini committed
853 854 855 856

    def render_java_class(self, _class):
        if _class is not None:
            try:
857
                javaclass = JavaClass(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
858 859 860 861 862 863 864 865 866 867 868
                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='.')
869
    argparser.add_argument('-p --package', type=str, help='the package name for the wrapper', dest='package', default='org.linphone.core')
870
    argparser.add_argument('-n --name', type=str, help='the name of the genarated source file', dest='name', default='linphone_jni.cc')
871
    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
872 873 874 875
    args = argparser.parse_args()

    srcdir = args.outputdir + '/src'
    javadir = args.outputdir + '/java'
876 877 878
    package_dirs = args.package.split('.')
    for directory in package_dirs:
        javadir += '/' + directory
Sylvain Berfini's avatar
Sylvain Berfini committed
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893

    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)

894
    genwrapper = GenWrapper(srcdir, javadir, args.package, args.xmldir, args.exceptions)
Sylvain Berfini's avatar
Sylvain Berfini committed
895 896 897 898
    genwrapper.render_all()

if __name__ == '__main__':
    main()