genwrapper.py 35.6 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
#!/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
21 22
import errno
import logging
Sylvain Berfini's avatar
Sylvain Berfini committed
23 24
import os
import pystache
25
import sys
Sylvain Berfini's avatar
Sylvain Berfini committed
26 27 28 29 30

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'tools'))
import genapixml as CApi
import abstractapi as AbsApi
import metadoc
31
import metaname
Sylvain Berfini's avatar
Sylvain Berfini committed
32

33

34 35 36 37 38 39 40 41 42 43 44
CORE_ACCESSOR_LIST = [
    'Call',
    'ChatRoom',
    'Event',
    'Friend',
    'FriendList',
    'NatPolicy',
    'Player',
    'ProxyConfig'
]

45 46 47 48 49 50 51 52 53 54 55 56

class JNINameTranslator(metaname.Translator):
    _instance = None

    @staticmethod
    def get():
        if JNINameTranslator._instance is None:
            JNINameTranslator._instance = JNINameTranslator()
        return JNINameTranslator._instance

    def translate_class_name(self, name, **params):
        translated_name = name.to_camel_case()
57
        if name.prev is not None and type(name.prev) is not metaname.NamespaceName:
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 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 125 126 127
            return name.prev.translate(self) + self._getseparator(name.prev) + translated_name
        else:
            return translated_name

    def translate_interface_name(self, name, **params):
        return self.translate_class_name(name, **params)

    def translate_enum_name(self, name, **params):
        return self.translate_class_name(name, **params)

    def translate_enumerator_name(self, name, **params):
        raise NotImplemented()

    def translate_method_name(self, name, **params):
        raise NotImplemented()

    def translate_namespace_name(self, name, **params):
        translated_name = name.to_snake_case()
        if name.prev is not None:
            return name.prev.translate(self) + self._getseparator(name.prev) + translated_name
        else:
            return translated_name

    def translate_argument_name(self, name, **params):
        raise NotImplemented()

    def translate_property_name(self, name, **params):
        raise NotImplemented()

    def _getseparator(self, previous_name):
        if isinstance(previous_name, metaname.NamespaceName):
            return '/'
        elif isinstance(previous_name, metaname.ClassName):
            return '$'
        else:
            raise TypeError("no separator for '{0}' type".format(type(previous_name)))


class JNILangTranslator(AbsApi.Translator):
    _instance = None

    @staticmethod
    def get():
        if JNILangTranslator._instance is None:
            JNILangTranslator._instance = JNILangTranslator()
        return JNILangTranslator._instance

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

128

Sylvain Berfini's avatar
Sylvain Berfini committed
129
class JavaTranslator(object):
130 131
    def __init__(self, packageName, exceptions):
        self.exceptions = exceptions
132 133
        package_dirs = packageName.split('.')
        self.jni_package = ''
134
        self.jni_path = ''
135 136
        for directory in package_dirs:
            self.jni_package += directory + '_'
137
            self.jni_path += directory + '/'
138

139 140
        self.nameTranslator = metaname.Translator.get('Java')
        self.langTranslator = AbsApi.Translator.get('Java')
141
        self.docTranslator = metadoc.JavaDocTranslator()
Sylvain Berfini's avatar
Sylvain Berfini committed
142

143 144 145 146 147
        self.jninameTranslator = JNINameTranslator.get()
        self.jnilangTranslator = JNILangTranslator.get()

        self.clangTranslator = AbsApi.Translator.get('C')

Sylvain Berfini's avatar
Sylvain Berfini committed
148
    def throws_exception(self, _type):
149 150
        if not self.exceptions:
            return False
Sylvain Berfini's avatar
Sylvain Berfini committed
151 152 153 154 155
        if type(_type) is AbsApi.BaseType:
            if _type.name == 'status':
                return True
        return False

156
    def translate_property(self, _property, _hasCoreAccessor):
Sylvain Berfini's avatar
Sylvain Berfini committed
157 158
        properties = []
        if _property.getter is not None:
159
            properties.append(self.translate_method(_property.getter, _hasCoreAccessor))
Sylvain Berfini's avatar
Sylvain Berfini committed
160
        if _property.setter is not None:
161
            properties.append(self.translate_method(_property.setter, _hasCoreAccessor))
Sylvain Berfini's avatar
Sylvain Berfini committed
162 163
        return properties

164
    def translate_jni_property(self, class_, _property):
165 166
        properties = []
        if _property.getter is not None:
167
            properties.append(self.translate_jni_method(class_, _property.getter))
168
        if _property.setter is not None:
169
            properties.append(self.translate_jni_method(class_, _property.setter))
170 171
        return properties

172 173 174 175 176 177 178
    def generate_listener(self, name, _class):
        methodDict = {}
        methodDict['return'] = 'void'
        methodDict['return_native'] = 'void'
        methodDict['return_keyword'] = ''
        methodDict['convertInputClassArrayToLongArray'] = False
        methodDict['name'] = name
Sylvain Berfini's avatar
Sylvain Berfini committed
179
        methodDict['name_native'] = name
180 181 182 183 184 185 186
        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'] = ''
187
        methodDict['native_params_impl'] = 'nativePtr, listener'
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202

        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)

203
    def translate_method(self, _method, _hasCoreAccessor=False):
Sylvain Berfini's avatar
Sylvain Berfini committed
204 205
        methodDict = {}

206 207 208 209
        namespace = _method.find_first_ancestor_by_type(AbsApi.Namespace)

        methodDict['return'] = _method.returnType.translate(self.langTranslator, isReturn=True, namespace=namespace)
        methodDict['return_native'] = _method.returnType.translate(self.langTranslator, native=True, isReturn=True, namespace=namespace)
Sylvain Berfini's avatar
Sylvain Berfini committed
210
        methodDict['return_keyword'] = '' if methodDict['return'] == 'void' else 'return '
211
        methodDict['hasReturn'] = not methodDict['return'] == 'void'
Sylvain Berfini's avatar
Sylvain Berfini committed
212 213 214 215

        methodDict['convertInputClassArrayToLongArray'] = False

        methodDict['name'] = _method.name.to_camel_case(lower=True)
Sylvain Berfini's avatar
Sylvain Berfini committed
216 217
        methodDict['name_native'] = methodDict['name']

218
        if _method.name.to_c()[-1] == '2':
Sylvain Berfini's avatar
Sylvain Berfini committed
219 220
            methodDict['name_native'] += "2"

221 222
        methodDict['isNotGetCore'] = not methodDict['name'] == 'getCore'
        methodDict['hasCoreAccessor'] = _hasCoreAccessor
Sylvain Berfini's avatar
Sylvain Berfini committed
223 224
        methodDict['exception'] = self.throws_exception(_method.returnType)

225
        methodDict['enumCast'] = type(_method.returnType) is AbsApi.EnumType
226
        methodDict['classCast'] = type(_method.returnType) is AbsApi.ClassType
227

228 229 230 231 232
        methodDict['params'] = ', '.join([arg.translate(self.langTranslator, namespace=namespace) for arg in _method.args])
        methodDict['native_params'] = ', '.join(['long nativePtr'] + [arg.translate(self.langTranslator, native=True, namespace=namespace) for arg in _method.args])
        methodDict['static_native_params'] = ', '.join([arg.translate(self.langTranslator, native=True, namespace=namespace) for arg in _method.args])
        methodDict['native_params_impl'] = ', '.join(
            ['nativePtr'] + [arg.name.translate(self.nameTranslator) + ('.toInt()' if type(arg.type) is AbsApi.EnumType else '') for arg in _method.args])
Sylvain Berfini's avatar
Sylvain Berfini committed
233 234

        methodDict['deprecated'] = _method.deprecated
235
        methodDict['doc'] = _method.briefDescription.translate(self.docTranslator) if _method.briefDescription is not None else None
Sylvain Berfini's avatar
Sylvain Berfini committed
236 237 238

        return methodDict

239
    def translate_jni_method(self, class_, _method, static=False):
240 241
        jni_blacklist = ['linphone_call_set_native_video_window_id',
                        'linphone_core_set_native_preview_window_id',
242 243
                        'linphone_core_set_native_video_window_id']

244 245 246
        namespace = class_.find_first_ancestor_by_type(AbsApi.Namespace)
        className = class_.name.translate(self.nameTranslator)

247
        methodDict = {'notEmpty': True}
248 249 250 251
        methodDict['classCName'] = class_.name.to_c()
        methodDict['className'] = className
        methodDict['classImplName'] = className + 'Impl'
        methodDict['isLinphoneFactory'] = (className == 'Factory')
252
        methodDict['jniPath'] = self.jni_path
Sylvain Berfini's avatar
Sylvain Berfini committed
253

254
        methodDict['return'] = _method.returnType.translate(self.langTranslator, jni=True, isReturn=True, namespace=namespace)
255
        methodDict['hasListReturn'] = methodDict['return'] == 'jobjectArray'
256 257
        methodDict['hasByteArrayReturn'] = methodDict['return'] == 'jbyteArray'
        methodDict['hasReturn'] = not methodDict['return'] == 'void' and not methodDict['hasListReturn'] and not methodDict['hasByteArrayReturn']
258
        methodDict['hasStringReturn'] = methodDict['return'] == 'jstring'
259
        methodDict['hasNormalReturn'] = not methodDict['hasListReturn'] and not methodDict['hasStringReturn'] and not methodDict['hasByteArrayReturn']
260
        methodDict['name'] = 'Java_' + self.jni_package + className + 'Impl_' + _method.name.translate(self.nameTranslator)
Sylvain Berfini's avatar
Sylvain Berfini committed
261
        methodDict['notStatic'] = not static
262 263
        methodDict['isConstList'] = _method.returnType.isconst
        methodDict['isNotConstList'] = not _method.returnType.isconst
264

265
        if _method.name.to_c()[-1] == '2':
Sylvain Berfini's avatar
Sylvain Berfini committed
266 267
            methodDict['name'] += "2"

268 269 270 271 272 273 274
        if _method.name.to_c() == 'linphone_factory_create_core':
            methodDict['c_name'] = 'linphone_factory_create_core_3'
        elif _method.name.to_c() == 'linphone_factory_create_core_with_config':
            methodDict['c_name'] = 'linphone_factory_create_core_with_config_3'
        else:
            methodDict['c_name'] = _method.name.to_c()

Sylvain Berfini's avatar
Sylvain Berfini committed
275
        methodDict['returnObject'] = methodDict['hasReturn'] and type(_method.returnType) is AbsApi.ClassType
276
        methodDict['returnClassName'] = _method.returnType.translate(self.langTranslator, namespace=namespace)
277 278
        methodDict['isRealObjectArray'] = False
        methodDict['isStringObjectArray'] = False
279 280
        methodDict['c_type_return'] = _method.returnType.translate(self.clangTranslator)

281 282
        if methodDict['c_name'] in jni_blacklist:
            return {'notEmpty': False}
283 284

        if methodDict['hasListReturn']:
285 286 287
            if type(_method.returnType) is AbsApi.BaseType and _method.returnType.name == 'string_array':
                methodDict['isStringObjectArray'] = True
            elif type(_method.returnType.containedTypeDesc) is AbsApi.BaseType:
288 289 290
                methodDict['isStringObjectArray'] = True
            elif type(_method.returnType.containedTypeDesc) is AbsApi.ClassType:
                methodDict['isRealObjectArray'] = True
291
                methodDict['objectCPrefix'] = 'linphone_' + _method.returnType.containedTypeDesc.desc.name.to_snake_case()
292 293 294
                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
295

296
        methodDict['params'] = 'JNIEnv *env, jobject thiz, jlong ptr'
Sylvain Berfini's avatar
Sylvain Berfini committed
297 298 299
        methodDict['params_impl'] = ''
        methodDict['strings'] = []
        methodDict['objects'] = []
300 301
        methodDict['lists'] = []
        methodDict['array'] = []
302
        methodDict['bytes'] = []
Sylvain Berfini's avatar
Sylvain Berfini committed
303
        methodDict['returnedObjectGetter'] = ''
304
        for arg in _method.args:
Sylvain Berfini's avatar
Sylvain Berfini committed
305
            methodDict['params'] += ', '
306 307 308 309 310 311
            if static:
                if arg is not _method.args[0]:
                    methodDict['params_impl'] += ', '
            else:
                methodDict['params_impl'] += ', '

312
            methodDict['params'] += arg.translate(self.langTranslator, jni=True, namespace=namespace)
313
            argname = arg.name.translate(self.nameTranslator)
Sylvain Berfini's avatar
Sylvain Berfini committed
314 315

            if type(arg.type) is AbsApi.ClassType:
316 317 318
                classCName = 'Linphone' + arg.type.desc.name.to_camel_case()
                if classCName[-8:] == 'Listener':
                   classCName = 'Linphone' + arg.type.desc.name.to_camel_case()[:-8] + 'Cbs'
319
                methodDict['objects'].append({'object': argname, 'objectClassCName': classCName})
Sylvain Berfini's avatar
Sylvain Berfini committed
320
                methodDict['params_impl'] += 'c_' + argname
321

322 323 324
            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
325
                methodDict['lists'].append({'list': argname, 'isStringList': isStringList, 'isObjList': isObjList, 'objectClassCName': arg.type.containedTypeDesc.name})
326
                methodDict['params_impl'] += 'bctbx_list_' + argname
327 328 329 330

            elif type(arg.type) is AbsApi.EnumType:
                argCType = arg.type.name
                methodDict['params_impl'] += '(' + argCType + ') ' + argname
331

Sylvain Berfini's avatar
Sylvain Berfini committed
332
            elif type(arg.type) is AbsApi.BaseType:
333
                if arg.type.name == 'integer' and arg.type.size is not None and arg.type.isref:
334
                    methodDict['bytes'].append({'bytesargname': argname, 'bytesargtype' : arg.type.translate(self.clangTranslator)})
335 336
                    methodDict['params_impl'] += 'c_' + argname
                elif arg.type.name == 'string':
Sylvain Berfini's avatar
Sylvain Berfini committed
337 338 339
                    methodDict['strings'].append({'string': argname})
                    methodDict['params_impl'] += 'c_' + argname
                else:
340
                    methodDict['params_impl'] += '(' + arg.type.translate(self.clangTranslator) + ')' + argname
Sylvain Berfini's avatar
Sylvain Berfini committed
341 342
            else:
                methodDict['params_impl'] += argname
343 344 345

        return methodDict

Sylvain Berfini's avatar
Sylvain Berfini committed
346 347
    def translate_class(self, _class):
        classDict = {
Sylvain Berfini's avatar
Sylvain Berfini committed
348 349
            'methods': [],
            'jniMethods': [],
Sylvain Berfini's avatar
Sylvain Berfini committed
350 351 352
        }

        classDict['isLinphoneFactory'] = _class.name.to_camel_case() == "Factory"
353
        classDict['isLinphoneCore'] = _class.name.to_camel_case() == "Core"
354 355
        hasCoreAccessor = _class.name.to_camel_case() in CORE_ACCESSOR_LIST
        classDict['hasCoreAccessor'] = hasCoreAccessor
356
        classDict['doc'] = _class.briefDescription.translate(self.docTranslator) if _class.briefDescription is not None else None
357
        classDict['refCountable'] = _class.refcountable
Sylvain Berfini's avatar
Sylvain Berfini committed
358 359 360

        for _property in _class.properties:
            try:
361
                classDict['methods'] += self.translate_property(_property, hasCoreAccessor)
362
                classDict['jniMethods'] += self.translate_jni_property(_class, _property)
Sylvain Berfini's avatar
Sylvain Berfini committed
363
            except AbsApi.Error as e:
364
                logging.error('error while translating {0} property: {1}'.format(_property.name.to_snake_case(), e.args[0]))
Sylvain Berfini's avatar
Sylvain Berfini committed
365 366 367

        for method in _class.instanceMethods:
            try:
368
                methodDict = self.translate_method(method, hasCoreAccessor)
369
                jniMethodDict = self.translate_jni_method(_class, method)
Sylvain Berfini's avatar
Sylvain Berfini committed
370
                classDict['methods'].append(methodDict)
371
                classDict['jniMethods'].append(jniMethodDict)
Sylvain Berfini's avatar
Sylvain Berfini committed
372
            except AbsApi.Error as e:
373
                logging.error('Could not translate {0}: {1}'.format(method.name.to_snake_case(fullName=True), e.args[0]))
Sylvain Berfini's avatar
Sylvain Berfini committed
374 375 376

        for method in _class.classMethods:
            try:
377
                methodDict = self.translate_method(method, hasCoreAccessor)
378
                jniMethodDict = self.translate_jni_method(_class, method, True)
379
                classDict['methods'].append(methodDict)
380
                classDict['jniMethods'].append(jniMethodDict)
Sylvain Berfini's avatar
Sylvain Berfini committed
381
            except AbsApi.Error as e:
382
                logging.error('Could not translate {0}: {1}'.format(method.name.to_snake_case(fullName=True), e.args[0]))
Sylvain Berfini's avatar
Sylvain Berfini committed
383

384 385 386 387 388 389 390 391 392
        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
393 394
        return classDict

395
    def translate_jni_interface(self, _class, className, _method):
396
        methodDict = {}
397 398 399
        methodDict['classCName'] = className.to_c()
        methodDict['className'] = className.translate(self.nameTranslator)
        methodDict['classImplName'] = methodDict['className'] + 'Impl'
400
        methodDict['jniPath'] = self.jni_path
401 402 403 404
        methodDict['cPrefix'] = _class.name.to_snake_case(fullName=True)
        methodDict['callbackName'] = '_{0}_cb'.format(_method.name.to_snake_case(fullName=True))
        methodDict['jname'] = _method.name.translate(self.nameTranslator)
        methodDict['return'] = _method.returnType.translate(self.clangTranslator)
405 406 407
        methodDict['jniUpcallMethod'] = 'CallVoidMethod'
        methodDict['isJniUpcallBasicType'] = False
        methodDict['isJniUpcallObject'] = False
408
        if type(_method.returnType) is AbsApi.ClassType:
409 410 411 412
            methodDict['jniUpcallMethod'] = 'CallObjectMethod'
            methodDict['isJniUpcallObject'] = True
            methodDict['jniUpcallType'] = 'jobject'
        elif type(_method.returnType) is AbsApi.BaseType:
413 414
            if not _method.returnType.name == 'void':
                methodDict['jniUpcallMethod'] = 'CallIntMethod'
415
                methodDict['jniUpcallType'] = _method.returnType.translate(self.langTranslator, jni=True)
416
                methodDict['isJniUpcallBasicType'] = True
417
        methodDict['returnIfFail'] = '' if  methodDict['return'] == 'void' else ' NULL'
418
        methodDict['hasReturn'] = not methodDict['return'] == 'void'
419 420 421
        methodDict['isSingleListener'] = not _class.multilistener
        methodDict['isMultiListener'] = _class.multilistener

422
        methodDict['firstParam'] = ''
423 424 425 426 427 428 429
        methodDict['jobjects'] = []
        methodDict['jenums'] = []
        methodDict['jstrings'] = []
        methodDict['params'] = ''
        methodDict['jparams'] = '('
        methodDict['params_impl'] = ''
        for arg in _method.args:
430
            argname = arg.name.translate(self.nameTranslator)
431 432 433
            if arg is not _method.args[0]:
                methodDict['params'] += ', '
                methodDict['params_impl'] += ', '
434
            else:
435
                methodDict['firstParam'] = argname
436

437
            methodDict['params'] += '{0} {1}'.format(arg.type.translate(self.clangTranslator), argname)
438 439 440 441

            if type(arg.type) is AbsApi.ClassType:
                methodDict['jparams'] += 'L' + self.jni_path + arg.type.desc.name.to_camel_case() + ';'
                methodDict['params_impl'] += 'j_' + argname
442
                methodDict['jobjects'].append({'objectName': argname, 'className': arg.type.desc.name.to_camel_case(), })
443
            elif type(arg.type) is AbsApi.BaseType:
444
                methodDict['jparams'] += arg.type.translate(self.jnilangTranslator)
445 446 447 448 449 450
                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:
451
                methodDict['jparams'] += 'L' + self.jni_path + arg.type.desc.name.translate(self.jninameTranslator) + ';'
452
                methodDict['params_impl'] += 'j_' + argname
453
                methodDict['jenums'].append({'enumName': argname, 'cEnumPrefix': arg.type.desc.name.to_snake_case(fullName=True)})
454 455 456
            elif type(arg.type) is AbsApi.ListType:
                methodDict['jparams'] += '[L' + self.jni_path + arg.type.containedTypeDesc.name + ';'
                methodDict['params_impl'] += 'NULL'
457 458 459 460 461

        methodDict['jparams'] += ')'
        if (methodDict['return'] == 'void'):
            methodDict['jparams'] += 'V'
        else:
462 463 464 465 466 467
            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
468 469 470

        return methodDict

Sylvain Berfini's avatar
Sylvain Berfini committed
471 472
    def translate_interface(self, _class):
        interfaceDict = {
Sylvain Berfini's avatar
Sylvain Berfini committed
473
            'methods': [],
474
            'jniMethods': [],
Sylvain Berfini's avatar
Sylvain Berfini committed
475 476
        }

477
        interfaceDict['doc'] = _class.briefDescription.translate(self.docTranslator)
Sylvain Berfini's avatar
Sylvain Berfini committed
478

479
        for method in _class.instanceMethods:
Sylvain Berfini's avatar
Sylvain Berfini committed
480
            interfaceDict['methods'].append(self.translate_method(method))
481
            interfaceDict['jniMethods'].append(self.translate_jni_interface(_class.listenedClass, _class.name, method))
Sylvain Berfini's avatar
Sylvain Berfini committed
482 483 484

        return interfaceDict

485
    def translate_enum(self, enum):
486 487 488
        enumDict = {
            'jniMethods': [],
        }
Sylvain Berfini's avatar
Sylvain Berfini committed
489

490 491
        enumDict['name'] = enum.name.to_camel_case()
        enumDict['doc'] = enum.briefDescription.translate(self.docTranslator)
Sylvain Berfini's avatar
Sylvain Berfini committed
492 493 494 495
        enumDict['values'] = []
        i = 0
        lastValue = None

496 497
        enumDict['jniPath'] = self.jni_path

498
        for enumerator in enum.enumerators:
Sylvain Berfini's avatar
Sylvain Berfini committed
499
            enumValDict = {}
500 501 502 503 504 505 506
            enumValDict['name'] = enumerator.name.to_camel_case()
            enumValDict['doc'] = enumerator.briefDescription.translate(self.docTranslator)
            if isinstance(enumerator.value, int):
                lastValue = enumerator.value
                enumValDict['value'] = str(enumerator.value)
            elif isinstance(enumerator.value, AbsApi.Flag):
                enumValDict['value'] = '1<<' + str(enumerator.value.position)
Sylvain Berfini's avatar
Sylvain Berfini committed
507 508 509 510 511 512 513
            else:
                if lastValue is not None:
                    enumValDict['value'] = lastValue + 1
                    lastValue += 1
                else:
                    enumValDict['value'] = i
            i += 1
514
            enumValDict['commarorsemicolon'] = ';' if i == len(enum.enumerators) else ','
Sylvain Berfini's avatar
Sylvain Berfini committed
515 516 517 518 519 520 521
            enumDict['values'].append(enumValDict)

        return enumDict

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

class JavaEnum(object):
522
    def __init__(self, package, _enum, translator):
523
        javaNameTranslator = metaname.Translator.get('Java')
Sylvain Berfini's avatar
Sylvain Berfini committed
524
        self._class = translator.translate_enum(_enum)
525
        self.packageName = package
526 527
        self.className = _enum.name.translate(javaNameTranslator)
        self.cPrefix = _enum.name.to_snake_case(fullName=True)
Sylvain Berfini's avatar
Sylvain Berfini committed
528 529 530
        self.filename = self.className + ".java"
        self.values = self._class['values']
        self.doc = self._class['doc']
531
        self.jniName = _enum.name.translate(JNINameTranslator.get())
Sylvain Berfini's avatar
Sylvain Berfini committed
532

533 534 535 536
class JniInterface(object):
    def __init__(self, javaClass, apiClass):
        self.isSingleListener = (not apiClass.multilistener)
        self.isMultiListener = (apiClass.multilistener)
537 538
        self.className = javaClass.className
        self.classCName = javaClass.cName
539 540 541
        self.cPrefix = javaClass.cPrefix
        self.callbacks = []
        listener = apiClass.listenerInterface
542
        for method in listener.instanceMethods:
543
            self.callbacks.append({
544
                'callbackName': '_{0}_cb'.format(method.name.to_snake_case(fullName=True)),
545 546 547
                'callback': method.name.to_snake_case()[3:], # Remove the on_
            })

Sylvain Berfini's avatar
Sylvain Berfini committed
548
class JavaInterface(object):
549
    def __init__(self, package, _interface, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
550
        self._class = translator.translate_interface(_interface)
551
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
552 553
        self.className = _interface.name.to_camel_case()
        self.filename = self.className + ".java"
554
        self.cPrefix = 'linphone_' + _interface.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
555 556 557
        self.imports = []
        self.methods = self._class['methods']
        self.doc = self._class['doc']
558
        self.jniMethods = self._class['jniMethods']
Sylvain Berfini's avatar
Sylvain Berfini committed
559 560 561 562 563 564 565 566 567 568

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):
569
    def __init__(self, package, _class, translator):
Sylvain Berfini's avatar
Sylvain Berfini committed
570 571
        self._class = translator.translate_class(_class)
        self.isLinphoneFactory = self._class['isLinphoneFactory']
572
        self.isLinphoneCore = self._class['isLinphoneCore']
573
        self.isNotLinphoneFactory = not self.isLinphoneFactory
574
        self.cName = _class.name.to_c()
575
        self.hasCoreAccessor = self._class['hasCoreAccessor']
576
        self.cPrefix = _class.name.to_snake_case(fullName=True)
577
        self.packageName = package
Sylvain Berfini's avatar
Sylvain Berfini committed
578 579
        self.className = _class.name.to_camel_case()
        self.classImplName = self.className + "Impl"
580
        self.refCountable = self._class['refCountable']
581
        self.factoryName = _class.name.to_snake_case()
Sylvain Berfini's avatar
Sylvain Berfini committed
582 583 584
        self.filename = self.className + ".java"
        self.imports = []
        self.methods = self._class['methods']
585
        self.jniMethods = self._class['jniMethods']
Sylvain Berfini's avatar
Sylvain Berfini committed
586 587
        self.doc = self._class['doc']
        self.enums = []
588 589
        for enum in _class.enums:
            self.enums.append(JavaEnum(package, enum, translator))
590 591 592
        self.jniInterface = None
        if _class.listenerInterface is not None:
            self.jniInterface = JniInterface(self, _class)
Sylvain Berfini's avatar
Sylvain Berfini committed
593 594 595 596 597 598 599

    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
600
    def __init__(self, package):
601
        self.enums = []
602
        self.interfaces = []
603
        self.callbacks = []
604
        self.objects = []
Sylvain Berfini's avatar
Sylvain Berfini committed
605 606 607
        self.methods = []
        self.jni_package = ''
        self.jni_path = ''
608
        self.coreListener = []
Sylvain Berfini's avatar
Sylvain Berfini committed
609 610 611 612 613
        package_dirs = package.split('.')
        for directory in package_dirs:
            self.jni_package += directory + '_'
            self.jni_path += directory + '/'

614 615 616 617 618 619 620 621 622 623
    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
624
    def add_object(self, javaClass):
625 626
        if javaClass.className == 'Factory':
            return
Sylvain Berfini's avatar
Sylvain Berfini committed
627 628 629 630 631 632 633
        obj = {
            'jniPrefix': self.jni_package,
            'jniPath': self.jni_path,
            'cPrefix': javaClass.cPrefix,
            'className': javaClass.className,
            'classCName': javaClass.cName,
            'classImplName': javaClass.classImplName,
634
            'refCountable': javaClass.refCountable
Sylvain Berfini's avatar
Sylvain Berfini committed
635 636
        }
        self.objects.append(obj)
Sylvain Berfini's avatar
Sylvain Berfini committed
637

638 639 640 641 642 643
        jniInterface = javaClass.jniInterface
        if jniInterface is not None:
            interface = {
                'isSingleListener': jniInterface.isSingleListener,
                'isMultiListener': jniInterface.isMultiListener,
                'classCName': jniInterface.classCName,
644
                'className': jniInterface.className,
645 646 647 648 649 650 651
                'cPrefix': jniInterface.cPrefix,
                'jniPackage': self.jni_package,
                'factoryName': javaClass.factoryName,
                'callbacksList': []
            }
            for callback in jniInterface.callbacks:
                interface['callbacksList'].append(callback)
652 653
                if obj['className'] == 'Core':
                    self.coreListener.append(callback)
654 655
            self.interfaces.append(interface)

656 657 658 659
    def add_callbacks(self, name, callbacks):
        for callback in callbacks:
            self.callbacks.append(callback)

Sylvain Berfini's avatar
Sylvain Berfini committed
660
    def add_methods(self, name, methods):
Sylvain Berfini's avatar
Sylvain Berfini committed
661 662
        for method in methods:
            self.methods.append(method)
Sylvain Berfini's avatar
Sylvain Berfini committed
663

664 665 666 667
class Proguard(object):
    def __init__(self, package):
        self.package = package
        self.classes = []
668 669
        self.enums = []
        self.listeners = []
670 671 672 673 674 675 676 677 678

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

679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
        for javaEnum in javaClass.enums:
            enumObj = {
                'package': self.package,
                'className': javaClass.className + "$" + javaEnum.className,
            }
            self.enums.append(enumObj)

    def add_enum(self, javaEnum):
        obj = {
            'package': self.package,
            'className': javaEnum.className,
        }
        self.enums.append(obj)

    def add_interface(self, javaInterface):
        obj = {
            'package': self.package,
            'className': javaInterface.className,
        }
        self.listeners.append(obj)

Sylvain Berfini's avatar
Sylvain Berfini committed
700 701 702
##########################################################################

class GenWrapper(object):
703
    def __init__(self, srcdir, javadir, package, xmldir, exceptions):
Sylvain Berfini's avatar
Sylvain Berfini committed
704 705
        self.srcdir = srcdir
        self.javadir = javadir
706
        self.package = package
707
        self.exceptions = exceptions
Sylvain Berfini's avatar
Sylvain Berfini committed
708 709 710 711 712

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

713
        self.parser = AbsApi.CParser(project)
714 715 716 717 718 719 720 721 722 723 724 725 726
        self.parser.functionBl = [
            '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',
            'linphone_core_get_current_vtable',
            'linphone_factory_get',
            'linphone_factory_clean',
            'linphone_call_zoom_video',
            'linphone_core_get_zrtp_cache_db',
            'linphone_config_get_range'
        ]
Sylvain Berfini's avatar
Sylvain Berfini committed
727
        self.parser.parse_all()
728
        self.translator = JavaTranslator(package, exceptions)
Sylvain Berfini's avatar
Sylvain Berfini committed
729
        self.renderer = pystache.Renderer()
Sylvain Berfini's avatar
Sylvain Berfini committed
730
        self.jni = Jni(package)
731
        self.proguard = Proguard(package)
Sylvain Berfini's avatar
Sylvain Berfini committed
732 733 734 735 736 737

        self.enums = {}
        self.interfaces = {}
        self.classes = {}

    def render_all(self):
738
        for _interface in self.parser.namespace.interfaces:
Sylvain Berfini's avatar
Sylvain Berfini committed
739
            self.render_java_interface(_interface)
740
        for _class in self.parser.namespace.classes:
Sylvain Berfini's avatar
Sylvain Berfini committed
741
            self.render_java_class(_class)
742 743
        for enum in self.parser.namespace.enums:
            self.render_java_enum(enum)
Sylvain Berfini's avatar
Sylvain Berfini committed
744

745
        for name, value in self.enums.items():
Sylvain Berfini's avatar
Sylvain Berfini committed
746
            self.render(value, self.javadir + '/' + value.filename)
747
            self.proguard.add_enum(value)
748
        for name, value in self.interfaces.items():
Sylvain Berfini's avatar
Sylvain Berfini committed
749
            self.render(value, self.javadir + '/' + value.filename)
750
            self.proguard.add_interface(value)
751
        for name, value in self.classes.items():
Sylvain Berfini's avatar
Sylvain Berfini committed
752
            self.render(value, self.javadir + '/' + value.filename)
Sylvain Berfini's avatar
Sylvain Berfini committed
753
            self.jni.add_object(value)
754
            self.proguard.add_class(value)
Sylvain Berfini's avatar
Sylvain Berfini committed
755 756

        self.render(self.jni, self.srcdir + '/linphone_jni.cc')
757
        self.render(self.proguard, self.srcdir + '/proguard.txt')
Sylvain Berfini's avatar
Sylvain Berfini committed
758 759 760 761 762 763 764 765 766 767 768 769 770 771 772

    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:
773
                javaenum = JavaEnum(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
774
                self.enums[javaenum.className] = javaenum
775
                self.jni.add_enum(javaenum)
Sylvain Berfini's avatar
Sylvain Berfini committed
776
            except AbsApi.Error as e:
777
                logging.error('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
Sylvain Berfini's avatar
Sylvain Berfini committed
778 779 780 781

    def render_java_interface(self, _class):
        if _class is not None:
            try:
782
                javainterface = JavaInterface(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
783 784
                self.interfaces[javainterface.className] = javainterface
                javaInterfaceStub = JavaInterfaceStub(javainterface)
785
                self.interfaces[javaInterfaceStub.classNameStub] = javaInterfaceStub
Sylvain Berfini's avatar
Sylvain Berfini committed
786
            except AbsApi.Error as e:
787
                logging.error('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
788
            self.jni.add_callbacks(javainterface.className, javainterface.jniMethods)
Sylvain Berfini's avatar
Sylvain Berfini committed
789 790 791 792

    def render_java_class(self, _class):
        if _class is not None:
            try:
793
                javaclass = JavaClass(self.package, _class, self.translator)
Sylvain Berfini's avatar
Sylvain Berfini committed
794
                self.classes[javaclass.className] = javaclass
795 796
                for enum in javaclass.enums:
                    self.jni.add_enum(enum)
Sylvain Berfini's avatar
Sylvain Berfini committed
797
            except AbsApi.Error as e:
798
                logging.error('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
Sylvain Berfini's avatar
Sylvain Berfini committed
799 800 801 802
            self.jni.add_methods(javaclass.className, javaclass.jniMethods)

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

803
if __name__ == '__main__':
Sylvain Berfini's avatar
Sylvain Berfini committed
804 805 806
    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='.')
807
    argparser.add_argument('-p --package', type=str, help='the package name for the wrapper', dest='package', default='org.linphone.core')
808
    argparser.add_argument('-n --name', type=str, help='the name of the genarated source file', dest='name', default='linphone_jni.cc')
809
    argparser.add_argument('-e --exceptions', type=bool, help='enable the wrapping of LinphoneStatus into CoreException', dest='exceptions', default=False)
810
    argparser.add_argument('-v --verbose', action='store_true', dest='verbose_mode', default=False, help='Verbose mode.')
Sylvain Berfini's avatar
Sylvain Berfini committed
811 812
    args = argparser.parse_args()

813 814 815
    loglevel = logging.INFO if args.verbose_mode else logging.ERROR
    logging.basicConfig(format='%(levelname)s[%(name)s]: %(message)s', level=loglevel)

Sylvain Berfini's avatar
Sylvain Berfini committed
816 817
    srcdir = args.outputdir + '/src'
    javadir = args.outputdir + '/java'
818 819 820
    package_dirs = args.package.split('.')
    for directory in package_dirs:
        javadir += '/' + directory
Sylvain Berfini's avatar
Sylvain Berfini committed
821 822 823 824 825

    try:
        os.makedirs(srcdir)
    except OSError as e:
        if e.errno != errno.EEXIST:
826
            logging.critical("Cannot create '{0}' dircetory: {1}".format(srcdir, e.strerror))
Sylvain Berfini's avatar
Sylvain Berfini committed
827 828 829 830 831 832
            sys.exit(1)

    try:
        os.makedirs(javadir)
    except OSError as e:
        if e.errno != errno.EEXIST:
833
            logging.critical("Cannot create '{0}' dircetory: {1}".format(javadir, e.strerror))
Sylvain Berfini's avatar
Sylvain Berfini committed
834 835
            sys.exit(1)

836
    genwrapper = GenWrapper(srcdir, javadir, args.package, args.xmldir, args.exceptions)
Sylvain Berfini's avatar
Sylvain Berfini committed
837
    genwrapper.render_all()