1 #!/usr/bin/env python 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """Extracts native methods from a Java file and generates the JNI bindings. 7 If you change this, please run and update the tests.""" 8 9 import collections 10 import errno 11 import optparse 12 import os 13 import re 14 import string 15 from string import Template 16 import subprocess 17 import sys 18 import textwrap 19 import zipfile 20 21 22 class ParseError(Exception): 23 """Exception thrown when we can't parse the input file.""" 24 25 def __init__(self, description, *context_lines): 26 Exception.__init__(self) 27 self.description = description 28 self.context_lines = context_lines 29 30 def __str__(self): 31 context = '\n'.join(self.context_lines) 32 return '***\nERROR: %s\n\n%s\n***' % (self.description, context) 33 34 35 class Param(object): 36 """Describes a param for a method, either java or native.""" 37 38 def __init__(self, **kwargs): 39 self.datatype = kwargs['datatype'] 40 self.name = kwargs['name'] 41 42 43 class NativeMethod(object): 44 """Describes a C/C++ method that is called by Java code""" 45 46 def __init__(self, **kwargs): 47 self.static = kwargs['static'] 48 self.java_class_name = kwargs['java_class_name'] 49 self.return_type = kwargs['return_type'] 50 self.name = kwargs['name'] 51 self.params = kwargs['params'] 52 if self.params: 53 assert type(self.params) is list 54 assert type(self.params[0]) is Param 55 if (self.params and 56 self.params[0].datatype == kwargs.get('ptr_type', 'int') and 57 self.params[0].name.startswith('native')): 58 self.type = 'method' 59 self.p0_type = self.params[0].name[len('native'):] 60 if kwargs.get('native_class_name'): 61 self.p0_type = kwargs['native_class_name'] 62 else: 63 self.type = 'function' 64 self.method_id_var_name = kwargs.get('method_id_var_name', None) 65 66 67 class CalledByNative(object): 68 """Describes a java method exported to c/c++""" 69 70 def __init__(self, **kwargs): 71 self.system_class = kwargs['system_class'] 72 self.unchecked = kwargs['unchecked'] 73 self.static = kwargs['static'] 74 self.java_class_name = kwargs['java_class_name'] 75 self.return_type = kwargs['return_type'] 76 self.name = kwargs['name'] 77 self.params = kwargs['params'] 78 self.method_id_var_name = kwargs.get('method_id_var_name', None) 79 self.signature = kwargs.get('signature') 80 self.is_constructor = kwargs.get('is_constructor', False) 81 self.env_call = GetEnvCall(self.is_constructor, self.static, 82 self.return_type) 83 self.static_cast = GetStaticCastForReturnType(self.return_type) 84 85 86 class ConstantField(object): 87 def __init__(self, **kwargs): 88 self.name = kwargs['name'] 89 self.value = kwargs['value'] 90 91 92 def JavaDataTypeToC(java_type): 93 """Returns a C datatype for the given java type.""" 94 java_pod_type_map = { 95 'int': 'jint', 96 'byte': 'jbyte', 97 'char': 'jchar', 98 'short': 'jshort', 99 'boolean': 'jboolean', 100 'long': 'jlong', 101 'double': 'jdouble', 102 'float': 'jfloat', 103 } 104 java_type_map = { 105 'void': 'void', 106 'String': 'jstring', 107 'java/lang/String': 'jstring', 108 'java/lang/Class': 'jclass', 109 } 110 111 if java_type in java_pod_type_map: 112 return java_pod_type_map[java_type] 113 elif java_type in java_type_map: 114 return java_type_map[java_type] 115 elif java_type.endswith('[]'): 116 if java_type[:-2] in java_pod_type_map: 117 return java_pod_type_map[java_type[:-2]] + 'Array' 118 return 'jobjectArray' 119 elif java_type.startswith('Class'): 120 # Checking just the start of the name, rather than a direct comparison, 121 # in order to handle generics. 122 return 'jclass' 123 else: 124 return 'jobject' 125 126 127 def JavaDataTypeToCForCalledByNativeParam(java_type): 128 """Returns a C datatype to be when calling from native.""" 129 if java_type == 'int': 130 return 'JniIntWrapper' 131 else: 132 return JavaDataTypeToC(java_type) 133 134 135 def JavaReturnValueToC(java_type): 136 """Returns a valid C return value for the given java type.""" 137 java_pod_type_map = { 138 'int': '0', 139 'byte': '0', 140 'char': '0', 141 'short': '0', 142 'boolean': 'false', 143 'long': '0', 144 'double': '0', 145 'float': '0', 146 'void': '' 147 } 148 return java_pod_type_map.get(java_type, 'NULL') 149 150 151 class JniParams(object): 152 _imports = [] 153 _fully_qualified_class = '' 154 _package = '' 155 _inner_classes = [] 156 _remappings = [] 157 _implicit_imports = [] 158 159 @staticmethod 160 def SetFullyQualifiedClass(fully_qualified_class): 161 JniParams._fully_qualified_class = 'L' + fully_qualified_class 162 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1]) 163 164 @staticmethod 165 def AddAdditionalImport(class_name): 166 assert class_name.endswith('.class') 167 raw_class_name = class_name[:-len('.class')] 168 if '.' in raw_class_name: 169 raise SyntaxError('%s cannot be used in @JNIAdditionalImport. ' 170 'Only import unqualified outer classes.' % class_name) 171 new_import = 'L%s/%s' % (JniParams._package, raw_class_name) 172 if new_import in JniParams._imports: 173 raise SyntaxError('Do not use JNIAdditionalImport on an already ' 174 'imported class: %s' % (new_import.replace('/', '.'))) 175 JniParams._imports += [new_import] 176 177 @staticmethod 178 def ExtractImportsAndInnerClasses(contents): 179 if not JniParams._package: 180 raise RuntimeError('SetFullyQualifiedClass must be called before ' 181 'ExtractImportsAndInnerClasses') 182 contents = contents.replace('\n', '') 183 re_import = re.compile(r'import.*?(?P<class>\S*?);') 184 for match in re.finditer(re_import, contents): 185 JniParams._imports += ['L' + match.group('class').replace('.', '/')] 186 187 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W') 188 for match in re.finditer(re_inner, contents): 189 inner = match.group('name') 190 if not JniParams._fully_qualified_class.endswith(inner): 191 JniParams._inner_classes += [JniParams._fully_qualified_class + '$' + 192 inner] 193 194 re_additional_imports = re.compile( 195 r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)') 196 for match in re.finditer(re_additional_imports, contents): 197 for class_name in match.group('class_names').split(','): 198 JniParams.AddAdditionalImport(class_name.strip()) 199 200 @staticmethod 201 def ParseJavaPSignature(signature_line): 202 prefix = 'Signature: ' 203 return '"%s"' % signature_line[signature_line.index(prefix) + len(prefix):] 204 205 @staticmethod 206 def JavaToJni(param): 207 """Converts a java param into a JNI signature type.""" 208 pod_param_map = { 209 'int': 'I', 210 'boolean': 'Z', 211 'char': 'C', 212 'short': 'S', 213 'long': 'J', 214 'double': 'D', 215 'float': 'F', 216 'byte': 'B', 217 'void': 'V', 218 } 219 object_param_list = [ 220 'Ljava/lang/Boolean', 221 'Ljava/lang/Integer', 222 'Ljava/lang/Long', 223 'Ljava/lang/Object', 224 'Ljava/lang/String', 225 'Ljava/lang/Class', 226 ] 227 228 prefix = '' 229 # Array? 230 while param[-2:] == '[]': 231 prefix += '[' 232 param = param[:-2] 233 # Generic? 234 if '<' in param: 235 param = param[:param.index('<')] 236 if param in pod_param_map: 237 return prefix + pod_param_map[param] 238 if '/' in param: 239 # Coming from javap, use the fully qualified param directly. 240 return prefix + 'L' + JniParams.RemapClassName(param) + ';' 241 242 for qualified_name in (object_param_list + 243 [JniParams._fully_qualified_class] + 244 JniParams._inner_classes): 245 if (qualified_name.endswith('/' + param) or 246 qualified_name.endswith('$' + param.replace('.', '$')) or 247 qualified_name == 'L' + param): 248 return prefix + JniParams.RemapClassName(qualified_name) + ';' 249 250 # Is it from an import? (e.g. referecing Class from import pkg.Class; 251 # note that referencing an inner class Inner from import pkg.Class.Inner 252 # is not supported). 253 for qualified_name in JniParams._imports: 254 if qualified_name.endswith('/' + param): 255 # Ensure it's not an inner class. 256 components = qualified_name.split('/') 257 if len(components) > 2 and components[-2][0].isupper(): 258 raise SyntaxError('Inner class (%s) can not be imported ' 259 'and used by JNI (%s). Please import the outer ' 260 'class and use Outer.Inner instead.' % 261 (qualified_name, param)) 262 return prefix + JniParams.RemapClassName(qualified_name) + ';' 263 264 # Is it an inner class from an outer class import? (e.g. referencing 265 # Class.Inner from import pkg.Class). 266 if '.' in param: 267 components = param.split('.') 268 outer = '/'.join(components[:-1]) 269 inner = components[-1] 270 for qualified_name in JniParams._imports: 271 if qualified_name.endswith('/' + outer): 272 return (prefix + JniParams.RemapClassName(qualified_name) + 273 '$' + inner + ';') 274 raise SyntaxError('Inner class (%s) can not be ' 275 'used directly by JNI. Please import the outer ' 276 'class, probably:\n' 277 'import %s.%s;' % 278 (param, JniParams._package.replace('/', '.'), 279 outer.replace('/', '.'))) 280 281 JniParams._CheckImplicitImports(param) 282 283 # Type not found, falling back to same package as this class. 284 return (prefix + 'L' + 285 JniParams.RemapClassName(JniParams._package + '/' + param) + ';') 286 287 @staticmethod 288 def _CheckImplicitImports(param): 289 # Ensure implicit imports, such as java.lang.*, are not being treated 290 # as being in the same package. 291 if not JniParams._implicit_imports: 292 # This file was generated from android.jar and lists 293 # all classes that are implicitly imported. 294 with file(os.path.join(os.path.dirname(sys.argv[0]), 295 'android_jar.classes'), 'r') as f: 296 JniParams._implicit_imports = f.readlines() 297 for implicit_import in JniParams._implicit_imports: 298 implicit_import = implicit_import.strip().replace('.class', '') 299 implicit_import = implicit_import.replace('/', '.') 300 if implicit_import.endswith('.' + param): 301 raise SyntaxError('Ambiguous class (%s) can not be used directly ' 302 'by JNI.\nPlease import it, probably:\n\n' 303 'import %s;' % 304 (param, implicit_import)) 305 306 307 @staticmethod 308 def Signature(params, returns, wrap): 309 """Returns the JNI signature for the given datatypes.""" 310 items = ['('] 311 items += [JniParams.JavaToJni(param.datatype) for param in params] 312 items += [')'] 313 items += [JniParams.JavaToJni(returns)] 314 if wrap: 315 return '\n' + '\n'.join(['"' + item + '"' for item in items]) 316 else: 317 return '"' + ''.join(items) + '"' 318 319 @staticmethod 320 def Parse(params): 321 """Parses the params into a list of Param objects.""" 322 if not params: 323 return [] 324 ret = [] 325 for p in [p.strip() for p in params.split(',')]: 326 items = p.split(' ') 327 if 'final' in items: 328 items.remove('final') 329 param = Param( 330 datatype=items[0], 331 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), 332 ) 333 ret += [param] 334 return ret 335 336 @staticmethod 337 def RemapClassName(class_name): 338 """Remaps class names using the jarjar mapping table.""" 339 for old, new in JniParams._remappings: 340 if old.endswith('**') and old[:-2] in class_name: 341 return class_name.replace(old[:-2], new, 1) 342 if '*' not in old and class_name.endswith(old): 343 return class_name.replace(old, new, 1) 344 345 return class_name 346 347 @staticmethod 348 def SetJarJarMappings(mappings): 349 """Parse jarjar mappings from a string.""" 350 JniParams._remappings = [] 351 for line in mappings.splitlines(): 352 rule = line.split() 353 if rule[0] != 'rule': 354 continue 355 _, src, dest = rule 356 src = src.replace('.', '/') 357 dest = dest.replace('.', '/') 358 if src.endswith('**'): 359 src_real_name = src[:-2] 360 else: 361 assert not '*' in src 362 src_real_name = src 363 364 if dest.endswith('@0'): 365 JniParams._remappings.append((src, dest[:-2] + src_real_name)) 366 elif dest.endswith('@1'): 367 assert '**' in src 368 JniParams._remappings.append((src, dest[:-2])) 369 else: 370 assert not '@' in dest 371 JniParams._remappings.append((src, dest)) 372 373 374 def ExtractJNINamespace(contents): 375 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') 376 m = re.findall(re_jni_namespace, contents) 377 if not m: 378 return '' 379 return m[0] 380 381 382 def ExtractFullyQualifiedJavaClassName(java_file_name, contents): 383 re_package = re.compile('.*?package (.*?);') 384 matches = re.findall(re_package, contents) 385 if not matches: 386 raise SyntaxError('Unable to find "package" line in %s' % java_file_name) 387 return (matches[0].replace('.', '/') + '/' + 388 os.path.splitext(os.path.basename(java_file_name))[0]) 389 390 391 def ExtractNatives(contents, ptr_type): 392 """Returns a list of dict containing information about a native method.""" 393 contents = contents.replace('\n', '') 394 natives = [] 395 re_native = re.compile(r'(@NativeClassQualifiedName' 396 '\(\"(?P<native_class_name>.*?)\"\)\s+)?' 397 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?' 398 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native ' 399 '(?P<return_type>\S*) ' 400 '(?P<name>native\w+)\((?P<params>.*?)\);') 401 for match in re.finditer(re_native, contents): 402 native = NativeMethod( 403 static='static' in match.group('qualifiers'), 404 java_class_name=match.group('java_class_name'), 405 native_class_name=match.group('native_class_name'), 406 return_type=match.group('return_type'), 407 name=match.group('name').replace('native', ''), 408 params=JniParams.Parse(match.group('params')), 409 ptr_type=ptr_type) 410 natives += [native] 411 return natives 412 413 414 def GetStaticCastForReturnType(return_type): 415 type_map = { 'String' : 'jstring', 416 'java/lang/String' : 'jstring', 417 'boolean[]': 'jbooleanArray', 418 'byte[]': 'jbyteArray', 419 'char[]': 'jcharArray', 420 'short[]': 'jshortArray', 421 'int[]': 'jintArray', 422 'long[]': 'jlongArray', 423 'float[]': 'jfloatArray', 424 'double[]': 'jdoubleArray' } 425 ret = type_map.get(return_type, None) 426 if ret: 427 return ret 428 if return_type.endswith('[]'): 429 return 'jobjectArray' 430 return None 431 432 433 def GetEnvCall(is_constructor, is_static, return_type): 434 """Maps the types availabe via env->Call__Method.""" 435 if is_constructor: 436 return 'NewObject' 437 env_call_map = {'boolean': 'Boolean', 438 'byte': 'Byte', 439 'char': 'Char', 440 'short': 'Short', 441 'int': 'Int', 442 'long': 'Long', 443 'float': 'Float', 444 'void': 'Void', 445 'double': 'Double', 446 'Object': 'Object', 447 } 448 call = env_call_map.get(return_type, 'Object') 449 if is_static: 450 call = 'Static' + call 451 return 'Call' + call + 'Method' 452 453 454 def GetMangledParam(datatype): 455 """Returns a mangled identifier for the datatype.""" 456 if len(datatype) <= 2: 457 return datatype.replace('[', 'A') 458 ret = '' 459 for i in range(1, len(datatype)): 460 c = datatype[i] 461 if c == '[': 462 ret += 'A' 463 elif c.isupper() or datatype[i - 1] in ['/', 'L']: 464 ret += c.upper() 465 return ret 466 467 468 def GetMangledMethodName(name, params, return_type): 469 """Returns a mangled method name for the given signature. 470 471 The returned name can be used as a C identifier and will be unique for all 472 valid overloads of the same method. 473 474 Args: 475 name: string. 476 params: list of Param. 477 return_type: string. 478 479 Returns: 480 A mangled name. 481 """ 482 mangled_items = [] 483 for datatype in [return_type] + [x.datatype for x in params]: 484 mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))] 485 mangled_name = name + '_'.join(mangled_items) 486 assert re.match(r'[0-9a-zA-Z_]+', mangled_name) 487 return mangled_name 488 489 490 def MangleCalledByNatives(called_by_natives): 491 """Mangles all the overloads from the call_by_natives list.""" 492 method_counts = collections.defaultdict( 493 lambda: collections.defaultdict(lambda: 0)) 494 for called_by_native in called_by_natives: 495 java_class_name = called_by_native.java_class_name 496 name = called_by_native.name 497 method_counts[java_class_name][name] += 1 498 for called_by_native in called_by_natives: 499 java_class_name = called_by_native.java_class_name 500 method_name = called_by_native.name 501 method_id_var_name = method_name 502 if method_counts[java_class_name][method_name] > 1: 503 method_id_var_name = GetMangledMethodName(method_name, 504 called_by_native.params, 505 called_by_native.return_type) 506 called_by_native.method_id_var_name = method_id_var_name 507 return called_by_natives 508 509 510 # Regex to match the JNI return types that should be included in a 511 # ScopedJavaLocalRef. 512 RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array') 513 514 # Regex to match a string like "@CalledByNative public void foo(int bar)". 515 RE_CALLED_BY_NATIVE = re.compile( 516 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?' 517 '\s+(?P<prefix>[\w ]*?)' 518 '\s*(?P<return_type>\S+?)' 519 '\s+(?P<name>\w+)' 520 '\s*\((?P<params>[^\)]*)\)') 521 522 523 def ExtractCalledByNatives(contents): 524 """Parses all methods annotated with @CalledByNative. 525 526 Args: 527 contents: the contents of the java file. 528 529 Returns: 530 A list of dict with information about the annotated methods. 531 TODO(bulach): return a CalledByNative object. 532 533 Raises: 534 ParseError: if unable to parse. 535 """ 536 called_by_natives = [] 537 for match in re.finditer(RE_CALLED_BY_NATIVE, contents): 538 called_by_natives += [CalledByNative( 539 system_class=False, 540 unchecked='Unchecked' in match.group('Unchecked'), 541 static='static' in match.group('prefix'), 542 java_class_name=match.group('annotation') or '', 543 return_type=match.group('return_type'), 544 name=match.group('name'), 545 params=JniParams.Parse(match.group('params')))] 546 # Check for any @CalledByNative occurrences that weren't matched. 547 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') 548 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): 549 if '@CalledByNative' in line1: 550 raise ParseError('could not parse @CalledByNative method signature', 551 line1, line2) 552 return MangleCalledByNatives(called_by_natives) 553 554 555 class JNIFromJavaP(object): 556 """Uses 'javap' to parse a .class file and generate the JNI header file.""" 557 558 def __init__(self, contents, options): 559 self.contents = contents 560 self.namespace = options.namespace 561 for line in contents: 562 class_name = re.match( 563 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)', 564 line) 565 if class_name: 566 self.fully_qualified_class = class_name.group('class_name') 567 break 568 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') 569 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip 570 # away the <...> and use the raw class name that Java 6 would've given us. 571 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0] 572 JniParams.SetFullyQualifiedClass(self.fully_qualified_class) 573 self.java_class_name = self.fully_qualified_class.split('/')[-1] 574 if not self.namespace: 575 self.namespace = 'JNI_' + self.java_class_name 576 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)' 577 '\((?P<params>.*?)\)') 578 self.called_by_natives = [] 579 for lineno, content in enumerate(contents[2:], 2): 580 match = re.match(re_method, content) 581 if not match: 582 continue 583 self.called_by_natives += [CalledByNative( 584 system_class=True, 585 unchecked=False, 586 static='static' in match.group('prefix'), 587 java_class_name='', 588 return_type=match.group('return_type').replace('.', '/'), 589 name=match.group('name'), 590 params=JniParams.Parse(match.group('params').replace('.', '/')), 591 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))] 592 re_constructor = re.compile('(.*?)public ' + 593 self.fully_qualified_class.replace('/', '.') + 594 '\((?P<params>.*?)\)') 595 for lineno, content in enumerate(contents[2:], 2): 596 match = re.match(re_constructor, content) 597 if not match: 598 continue 599 self.called_by_natives += [CalledByNative( 600 system_class=True, 601 unchecked=False, 602 static=False, 603 java_class_name='', 604 return_type=self.fully_qualified_class, 605 name='Constructor', 606 params=JniParams.Parse(match.group('params').replace('.', '/')), 607 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]), 608 is_constructor=True)] 609 self.called_by_natives = MangleCalledByNatives(self.called_by_natives) 610 611 self.constant_fields = [] 612 re_constant_field = re.compile('.*?public static final int (?P<name>.*?);') 613 re_constant_field_value = re.compile( 614 '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)') 615 for lineno, content in enumerate(contents[2:], 2): 616 match = re.match(re_constant_field, content) 617 if not match: 618 continue 619 value = re.match(re_constant_field_value, contents[lineno + 2]) 620 if not value: 621 value = re.match(re_constant_field_value, contents[lineno + 3]) 622 if value: 623 self.constant_fields.append( 624 ConstantField(name=match.group('name'), 625 value=value.group('value'))) 626 627 self.inl_header_file_generator = InlHeaderFileGenerator( 628 self.namespace, self.fully_qualified_class, [], 629 self.called_by_natives, self.constant_fields, options) 630 631 def GetContent(self): 632 return self.inl_header_file_generator.GetContent() 633 634 @staticmethod 635 def CreateFromClass(class_file, options): 636 class_name = os.path.splitext(os.path.basename(class_file))[0] 637 p = subprocess.Popen(args=[options.javap, '-c', '-verbose', 638 '-s', class_name], 639 cwd=os.path.dirname(class_file), 640 stdout=subprocess.PIPE, 641 stderr=subprocess.PIPE) 642 stdout, _ = p.communicate() 643 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options) 644 return jni_from_javap 645 646 647 class JNIFromJavaSource(object): 648 """Uses the given java source file to generate the JNI header file.""" 649 650 # Match single line comments, multiline comments, character literals, and 651 # double-quoted strings. 652 _comment_remover_regex = re.compile( 653 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 654 re.DOTALL | re.MULTILINE) 655 656 def __init__(self, contents, fully_qualified_class, options): 657 contents = self._RemoveComments(contents) 658 JniParams.SetFullyQualifiedClass(fully_qualified_class) 659 JniParams.ExtractImportsAndInnerClasses(contents) 660 jni_namespace = ExtractJNINamespace(contents) or options.namespace 661 natives = ExtractNatives(contents, options.ptr_type) 662 called_by_natives = ExtractCalledByNatives(contents) 663 if len(natives) == 0 and len(called_by_natives) == 0: 664 raise SyntaxError('Unable to find any JNI methods for %s.' % 665 fully_qualified_class) 666 inl_header_file_generator = InlHeaderFileGenerator( 667 jni_namespace, fully_qualified_class, natives, called_by_natives, 668 [], options) 669 self.content = inl_header_file_generator.GetContent() 670 671 @classmethod 672 def _RemoveComments(cls, contents): 673 # We need to support both inline and block comments, and we need to handle 674 # strings that contain '//' or '/*'. 675 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java 676 # parser. Maybe we could ditch JNIFromJavaSource and just always use 677 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. 678 # http://code.google.com/p/chromium/issues/detail?id=138941 679 def replacer(match): 680 # Replace matches that are comments with nothing; return literals/strings 681 # unchanged. 682 s = match.group(0) 683 if s.startswith('/'): 684 return '' 685 else: 686 return s 687 return cls._comment_remover_regex.sub(replacer, contents) 688 689 def GetContent(self): 690 return self.content 691 692 @staticmethod 693 def CreateFromFile(java_file_name, options): 694 contents = file(java_file_name).read() 695 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, 696 contents) 697 return JNIFromJavaSource(contents, fully_qualified_class, options) 698 699 700 class InlHeaderFileGenerator(object): 701 """Generates an inline header file for JNI integration.""" 702 703 def __init__(self, namespace, fully_qualified_class, natives, 704 called_by_natives, constant_fields, options): 705 self.namespace = namespace 706 self.fully_qualified_class = fully_qualified_class 707 self.class_name = self.fully_qualified_class.split('/')[-1] 708 self.natives = natives 709 self.called_by_natives = called_by_natives 710 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' 711 self.constant_fields = constant_fields 712 self.options = options 713 self.init_native = self.ExtractInitNative(options) 714 715 def ExtractInitNative(self, options): 716 for native in self.natives: 717 if options.jni_init_native_name == 'native' + native.name: 718 self.natives.remove(native) 719 return native 720 return None 721 722 def GetContent(self): 723 """Returns the content of the JNI binding file.""" 724 template = Template("""\ 725 // Copyright 2014 The Chromium Authors. All rights reserved. 726 // Use of this source code is governed by a BSD-style license that can be 727 // found in the LICENSE file. 728 729 730 // This file is autogenerated by 731 // ${SCRIPT_NAME} 732 // For 733 // ${FULLY_QUALIFIED_CLASS} 734 735 #ifndef ${HEADER_GUARD} 736 #define ${HEADER_GUARD} 737 738 #include <jni.h> 739 740 ${INCLUDES} 741 742 #include "base/android/jni_int_wrapper.h" 743 744 // Step 1: forward declarations. 745 namespace { 746 $CLASS_PATH_DEFINITIONS 747 $METHOD_ID_DEFINITIONS 748 } // namespace 749 750 $OPEN_NAMESPACE 751 $FORWARD_DECLARATIONS 752 753 $CONSTANT_FIELDS 754 755 // Step 2: method stubs. 756 $METHOD_STUBS 757 758 // Step 3: RegisterNatives. 759 $JNI_NATIVE_METHODS 760 $REGISTER_NATIVES 761 $CLOSE_NAMESPACE 762 $JNI_REGISTER_NATIVES 763 #endif // ${HEADER_GUARD} 764 """) 765 values = { 766 'SCRIPT_NAME': self.options.script_name, 767 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, 768 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), 769 'METHOD_ID_DEFINITIONS': self.GetMethodIDDefinitionsString(), 770 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(), 771 'CONSTANT_FIELDS': self.GetConstantFieldsString(), 772 'METHOD_STUBS': self.GetMethodStubsString(), 773 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), 774 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(), 775 'REGISTER_NATIVES': self.GetRegisterNativesString(), 776 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), 777 'HEADER_GUARD': self.header_guard, 778 'INCLUDES': self.GetIncludesString(), 779 'JNI_REGISTER_NATIVES': self.GetJNIRegisterNativesString() 780 } 781 return WrapOutput(template.substitute(values)) 782 783 def GetClassPathDefinitionsString(self): 784 ret = [] 785 ret += [self.GetClassPathDefinitions()] 786 return '\n'.join(ret) 787 788 def GetMethodIDDefinitionsString(self): 789 """Returns the definition of method ids for the called by native methods.""" 790 if not self.options.eager_called_by_natives: 791 return '' 792 template = Template("""\ 793 jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = NULL;""") 794 ret = [] 795 for called_by_native in self.called_by_natives: 796 values = { 797 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, 798 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, 799 } 800 ret += [template.substitute(values)] 801 return '\n'.join(ret) 802 803 def GetForwardDeclarationsString(self): 804 ret = [] 805 for native in self.natives: 806 if native.type != 'method': 807 ret += [self.GetForwardDeclaration(native)] 808 if self.options.native_exports and ret: 809 return '\nextern "C" {\n' + "\n".join(ret) + '\n}; // extern "C"' 810 return '\n'.join(ret) 811 812 def GetConstantFieldsString(self): 813 if not self.constant_fields: 814 return '' 815 ret = ['enum Java_%s_constant_fields {' % self.class_name] 816 for c in self.constant_fields: 817 ret += [' %s = %s,' % (c.name, c.value)] 818 ret += ['};'] 819 return '\n'.join(ret) 820 821 def GetMethodStubsString(self): 822 """Returns the code corresponding to method stubs.""" 823 ret = [] 824 for native in self.natives: 825 if native.type == 'method': 826 ret += [self.GetNativeMethodStubString(native)] 827 if self.options.eager_called_by_natives: 828 ret += self.GetEagerCalledByNativeMethodStubs() 829 else: 830 ret += self.GetLazyCalledByNativeMethodStubs() 831 832 if self.options.native_exports and ret: 833 return '\nextern "C" {\n' + "\n".join(ret) + '\n}; // extern "C"' 834 return '\n'.join(ret) 835 836 def GetLazyCalledByNativeMethodStubs(self): 837 return [self.GetLazyCalledByNativeMethodStub(called_by_native) 838 for called_by_native in self.called_by_natives] 839 840 def GetEagerCalledByNativeMethodStubs(self): 841 ret = [] 842 if self.called_by_natives: 843 ret += ['namespace {'] 844 for called_by_native in self.called_by_natives: 845 ret += [self.GetEagerCalledByNativeMethodStub(called_by_native)] 846 ret += ['} // namespace'] 847 return ret 848 849 def GetIncludesString(self): 850 if not self.options.includes: 851 return '' 852 includes = self.options.includes.split(',') 853 return '\n'.join('#include "%s"' % x for x in includes) 854 855 def GetKMethodsString(self, clazz): 856 ret = [] 857 for native in self.natives: 858 if (native.java_class_name == clazz or 859 (not native.java_class_name and clazz == self.class_name)): 860 ret += [self.GetKMethodArrayEntry(native)] 861 return '\n'.join(ret) 862 863 def SubstituteNativeMethods(self, template): 864 """Substitutes JAVA_CLASS and KMETHODS in the provided template.""" 865 ret = [] 866 all_classes = self.GetUniqueClasses(self.natives) 867 all_classes[self.class_name] = self.fully_qualified_class 868 for clazz in all_classes: 869 kmethods = self.GetKMethodsString(clazz) 870 if kmethods: 871 values = {'JAVA_CLASS': clazz, 872 'KMETHODS': kmethods} 873 ret += [template.substitute(values)] 874 if not ret: return '' 875 return '\n' + '\n'.join(ret) 876 877 def GetJNINativeMethodsString(self): 878 """Returns the implementation of the array of native methods.""" 879 if self.options.native_exports: 880 return '' 881 template = Template("""\ 882 static const JNINativeMethod kMethods${JAVA_CLASS}[] = { 883 ${KMETHODS} 884 }; 885 """) 886 return self.SubstituteNativeMethods(template) 887 888 def GetRegisterCalledByNativesImplString(self): 889 """Returns the code for registering the called by native methods.""" 890 if not self.options.eager_called_by_natives: 891 return '' 892 template = Template("""\ 893 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = ${GET_METHOD_ID_IMPL} 894 if (g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} == NULL) { 895 return false; 896 } 897 """) 898 ret = [] 899 for called_by_native in self.called_by_natives: 900 values = { 901 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, 902 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, 903 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native), 904 } 905 ret += [template.substitute(values)] 906 return '\n'.join(ret) 907 908 def GetRegisterNativesString(self): 909 """Returns the code for RegisterNatives.""" 910 template = Template("""\ 911 ${REGISTER_NATIVES_SIGNATURE} { 912 ${CLASSES} 913 ${NATIVES} 914 ${CALLED_BY_NATIVES} 915 return true; 916 } 917 """) 918 signature = 'static bool RegisterNativesImpl(JNIEnv* env' 919 if self.init_native: 920 signature += ', jclass clazz)' 921 else: 922 signature += ')' 923 924 natives = self.GetRegisterNativesImplString() 925 called_by_natives = self.GetRegisterCalledByNativesImplString() 926 values = {'REGISTER_NATIVES_SIGNATURE': signature, 927 'CLASSES': self.GetFindClasses(), 928 'NATIVES': natives, 929 'CALLED_BY_NATIVES': called_by_natives, 930 } 931 return template.substitute(values) 932 933 def GetRegisterNativesImplString(self): 934 """Returns the shared implementation for RegisterNatives.""" 935 if self.options.native_exports: 936 return '' 937 938 template = Template("""\ 939 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS}); 940 941 if (env->RegisterNatives(${JAVA_CLASS}_clazz(env), 942 kMethods${JAVA_CLASS}, 943 kMethods${JAVA_CLASS}Size) < 0) { 944 jni_generator::HandleRegistrationError( 945 env, ${JAVA_CLASS}_clazz(env), __FILE__); 946 return false; 947 } 948 """) 949 return self.SubstituteNativeMethods(template) 950 951 def GetJNIRegisterNativesString(self): 952 """Returns the implementation for the JNI registration of native methods.""" 953 if not self.init_native: 954 return '' 955 956 template = Template("""\ 957 extern "C" JNIEXPORT bool JNICALL 958 Java_${FULLY_QUALIFIED_CLASS}_${INIT_NATIVE_NAME}(JNIEnv* env, jclass clazz) { 959 return ${NAMESPACE}RegisterNativesImpl(env, clazz); 960 } 961 """) 962 963 if self.options.native_exports: 964 java_name = JniParams.RemapClassName(self.fully_qualified_class) 965 java_name = java_name.replace('_', '_1').replace('/', '_') 966 else: 967 java_name = self.fully_qualified_class.replace('/', '_') 968 969 namespace = '' 970 if self.namespace: 971 namespace = self.namespace + '::' 972 values = {'FULLY_QUALIFIED_CLASS': java_name, 973 'INIT_NATIVE_NAME': 'native' + self.init_native.name, 974 'NAMESPACE': namespace, 975 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString() 976 } 977 return template.substitute(values) 978 979 def GetOpenNamespaceString(self): 980 if self.namespace: 981 all_namespaces = ['namespace %s {' % ns 982 for ns in self.namespace.split('::')] 983 return '\n'.join(all_namespaces) 984 return '' 985 986 def GetCloseNamespaceString(self): 987 if self.namespace: 988 all_namespaces = ['} // namespace %s' % ns 989 for ns in self.namespace.split('::')] 990 all_namespaces.reverse() 991 return '\n'.join(all_namespaces) + '\n' 992 return '' 993 994 def GetJNIFirstParam(self, native): 995 ret = [] 996 if native.type == 'method': 997 ret = ['jobject jcaller'] 998 elif native.type == 'function': 999 if native.static: 1000 ret = ['jclass jcaller'] 1001 else: 1002 ret = ['jobject jcaller'] 1003 return ret 1004 1005 def GetParamsInDeclaration(self, native): 1006 """Returns the params for the stub declaration. 1007 1008 Args: 1009 native: the native dictionary describing the method. 1010 1011 Returns: 1012 A string containing the params. 1013 """ 1014 return ',\n '.join(self.GetJNIFirstParam(native) + 1015 [JavaDataTypeToC(param.datatype) + ' ' + 1016 param.name 1017 for param in native.params]) 1018 1019 def GetCalledByNativeParamsInDeclaration(self, called_by_native): 1020 return ',\n '.join([ 1021 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' + 1022 param.name 1023 for param in called_by_native.params]) 1024 1025 def GetForwardDeclaration(self, native): 1026 template_str = """ 1027 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS}); 1028 """ 1029 if self.options.native_exports: 1030 template_str += """ 1031 __attribute__((visibility("default"))) 1032 ${RETURN} Java_${JAVA_NAME}_native${NAME}(JNIEnv* env, ${PARAMS}) { 1033 return ${NAME}(${PARAMS_IN_CALL}); 1034 } 1035 """ 1036 template = Template(template_str) 1037 params_in_call = [] 1038 if not self.options.pure_native_methods: 1039 params_in_call = ['env', 'jcaller'] 1040 params_in_call = ', '.join(params_in_call + [p.name for p in native.params]) 1041 1042 java_name = JniParams.RemapClassName(self.fully_qualified_class) 1043 java_name = java_name.replace('_', '_1').replace('/', '_') 1044 if native.java_class_name: 1045 java_name += '_00024' + native.java_class_name 1046 1047 values = {'RETURN': JavaDataTypeToC(native.return_type), 1048 'NAME': native.name, 1049 'JAVA_NAME': java_name, 1050 'PARAMS': self.GetParamsInDeclaration(native), 1051 'PARAMS_IN_CALL': params_in_call} 1052 return template.substitute(values) 1053 1054 def GetNativeMethodStubString(self, native): 1055 """Returns stubs for native methods.""" 1056 if self.options.native_exports: 1057 template_str = """\ 1058 __attribute__((visibility("default"))) 1059 ${RETURN} Java_${JAVA_NAME}_native${NAME}(JNIEnv* env, 1060 ${PARAMS_IN_DECLARATION}) {""" 1061 else: 1062 template_str = """\ 1063 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {""" 1064 template_str += """ 1065 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); 1066 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN}); 1067 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL}; 1068 } 1069 """ 1070 1071 template = Template(template_str) 1072 params = [] 1073 if not self.options.pure_native_methods: 1074 params = ['env', 'jcaller'] 1075 params_in_call = ', '.join(params + [p.name for p in native.params[1:]]) 1076 1077 return_type = JavaDataTypeToC(native.return_type) 1078 optional_error_return = JavaReturnValueToC(native.return_type) 1079 if optional_error_return: 1080 optional_error_return = ', ' + optional_error_return 1081 post_call = '' 1082 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): 1083 post_call = '.Release()' 1084 1085 if self.options.native_exports: 1086 java_name = JniParams.RemapClassName(self.fully_qualified_class) 1087 java_name = java_name.replace('_', '_1').replace('/', '_') 1088 if native.java_class_name: 1089 java_name += '_00024' + native.java_class_name 1090 else: 1091 java_name = '' 1092 1093 values = { 1094 'RETURN': return_type, 1095 'OPTIONAL_ERROR_RETURN': optional_error_return, 1096 'JAVA_NAME': java_name, 1097 'NAME': native.name, 1098 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native), 1099 'PARAM0_NAME': native.params[0].name, 1100 'P0_TYPE': native.p0_type, 1101 'PARAMS_IN_CALL': params_in_call, 1102 'POST_CALL': post_call 1103 } 1104 return template.substitute(values) 1105 1106 def GetArgument(self, param): 1107 return ('as_jint(' + param.name + ')' 1108 if param.datatype == 'int' else param.name) 1109 1110 def GetArgumentsInCall(self, params): 1111 """Return a string of arguments to call from native into Java""" 1112 return [self.GetArgument(p) for p in params] 1113 1114 def GetCalledByNativeValues(self, called_by_native): 1115 """Fills in necessary values for the CalledByNative methods.""" 1116 java_class = called_by_native.java_class_name or self.class_name 1117 if called_by_native.static or called_by_native.is_constructor: 1118 first_param_in_declaration = '' 1119 first_param_in_call = ('%s_clazz(env)' % java_class) 1120 else: 1121 first_param_in_declaration = ', jobject obj' 1122 first_param_in_call = 'obj' 1123 params_in_declaration = self.GetCalledByNativeParamsInDeclaration( 1124 called_by_native) 1125 if params_in_declaration: 1126 params_in_declaration = ', ' + params_in_declaration 1127 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params)) 1128 if params_in_call: 1129 params_in_call = ', ' + params_in_call 1130 pre_call = '' 1131 post_call = '' 1132 if called_by_native.static_cast: 1133 pre_call = 'static_cast<%s>(' % called_by_native.static_cast 1134 post_call = ')' 1135 check_exception = '' 1136 if not called_by_native.unchecked: 1137 check_exception = 'jni_generator::CheckException(env);' 1138 return_type = JavaDataTypeToC(called_by_native.return_type) 1139 optional_error_return = JavaReturnValueToC(called_by_native.return_type) 1140 if optional_error_return: 1141 optional_error_return = ', ' + optional_error_return 1142 return_declaration = '' 1143 return_clause = '' 1144 if return_type != 'void': 1145 pre_call = ' ' + pre_call 1146 return_declaration = return_type + ' ret =' 1147 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): 1148 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>' 1149 return_clause = 'return ' + return_type + '(env, ret);' 1150 else: 1151 return_clause = 'return ret;' 1152 return { 1153 'JAVA_CLASS': java_class, 1154 'RETURN_TYPE': return_type, 1155 'OPTIONAL_ERROR_RETURN': optional_error_return, 1156 'RETURN_DECLARATION': return_declaration, 1157 'RETURN_CLAUSE': return_clause, 1158 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, 1159 'PARAMS_IN_DECLARATION': params_in_declaration, 1160 'PRE_CALL': pre_call, 1161 'POST_CALL': post_call, 1162 'ENV_CALL': called_by_native.env_call, 1163 'FIRST_PARAM_IN_CALL': first_param_in_call, 1164 'PARAMS_IN_CALL': params_in_call, 1165 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, 1166 'CHECK_EXCEPTION': check_exception, 1167 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native) 1168 } 1169 1170 def GetEagerCalledByNativeMethodStub(self, called_by_native): 1171 """Returns the implementation of the called by native method.""" 1172 template = Template(""" 1173 static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\ 1174 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION}) { 1175 ${RETURN_DECLARATION}${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, 1176 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL}; 1177 ${RETURN_CLAUSE} 1178 }""") 1179 values = self.GetCalledByNativeValues(called_by_native) 1180 return template.substitute(values) 1181 1182 def GetLazyCalledByNativeMethodStub(self, called_by_native): 1183 """Returns a string.""" 1184 function_signature_template = Template("""\ 1185 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\ 1186 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") 1187 function_header_template = Template("""\ 1188 ${FUNCTION_SIGNATURE} {""") 1189 function_header_with_unused_template = Template("""\ 1190 ${FUNCTION_SIGNATURE} __attribute__ ((unused)); 1191 ${FUNCTION_SIGNATURE} {""") 1192 template = Template(""" 1193 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0; 1194 ${FUNCTION_HEADER} 1195 /* Must call RegisterNativesImpl() */ 1196 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL}, 1197 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN}); 1198 jmethodID method_id = 1199 ${GET_METHOD_ID_IMPL} 1200 ${RETURN_DECLARATION} 1201 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, 1202 method_id${PARAMS_IN_CALL})${POST_CALL}; 1203 ${CHECK_EXCEPTION} 1204 ${RETURN_CLAUSE} 1205 }""") 1206 values = self.GetCalledByNativeValues(called_by_native) 1207 values['FUNCTION_SIGNATURE'] = ( 1208 function_signature_template.substitute(values)) 1209 if called_by_native.system_class: 1210 values['FUNCTION_HEADER'] = ( 1211 function_header_with_unused_template.substitute(values)) 1212 else: 1213 values['FUNCTION_HEADER'] = function_header_template.substitute(values) 1214 return template.substitute(values) 1215 1216 def GetKMethodArrayEntry(self, native): 1217 template = Template("""\ 1218 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""") 1219 values = {'NAME': native.name, 1220 'JNI_SIGNATURE': JniParams.Signature(native.params, 1221 native.return_type, 1222 True)} 1223 return template.substitute(values) 1224 1225 def GetUniqueClasses(self, origin): 1226 ret = {self.class_name: self.fully_qualified_class} 1227 for entry in origin: 1228 class_name = self.class_name 1229 jni_class_path = self.fully_qualified_class 1230 if entry.java_class_name: 1231 class_name = entry.java_class_name 1232 jni_class_path = self.fully_qualified_class + '$' + class_name 1233 ret[class_name] = jni_class_path 1234 return ret 1235 1236 def GetClassPathDefinitions(self): 1237 """Returns the ClassPath constants.""" 1238 ret = [] 1239 template = Template("""\ 1240 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""") 1241 native_classes = self.GetUniqueClasses(self.natives) 1242 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives) 1243 if self.options.native_exports: 1244 all_classes = called_by_native_classes 1245 else: 1246 all_classes = native_classes 1247 all_classes.update(called_by_native_classes) 1248 1249 for clazz in all_classes: 1250 values = { 1251 'JAVA_CLASS': clazz, 1252 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]), 1253 } 1254 ret += [template.substitute(values)] 1255 ret += '' 1256 1257 class_getter_methods = [] 1258 if self.options.native_exports: 1259 template = Template("""\ 1260 // Leaking this jclass as we cannot use LazyInstance from some threads. 1261 base::subtle::AtomicWord g_${JAVA_CLASS}_clazz = 0; 1262 #define ${JAVA_CLASS}_clazz(env) \ 1263 base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \ 1264 &g_${JAVA_CLASS}_clazz)""") 1265 else: 1266 template = Template("""\ 1267 // Leaking this jclass as we cannot use LazyInstance from some threads. 1268 jclass g_${JAVA_CLASS}_clazz = NULL; 1269 #define ${JAVA_CLASS}_clazz(env) g_${JAVA_CLASS}_clazz""") 1270 1271 for clazz in called_by_native_classes: 1272 values = { 1273 'JAVA_CLASS': clazz, 1274 } 1275 ret += [template.substitute(values)] 1276 1277 return '\n'.join(ret) 1278 1279 def GetFindClasses(self): 1280 """Returns the imlementation of FindClass for all known classes.""" 1281 if self.init_native: 1282 if self.options.native_exports: 1283 template = Template("""\ 1284 base::subtle::Release_Store(&g_${JAVA_CLASS}_clazz, 1285 static_cast<base::subtle::AtomicWord>(env->NewWeakGlobalRef(clazz));""") 1286 else: 1287 template = Template("""\ 1288 g_${JAVA_CLASS}_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));""") 1289 else: 1290 if self.options.native_exports: 1291 return '\n' 1292 template = Template("""\ 1293 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( 1294 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""") 1295 ret = [] 1296 for clazz in self.GetUniqueClasses(self.called_by_natives): 1297 values = {'JAVA_CLASS': clazz} 1298 ret += [template.substitute(values)] 1299 return '\n'.join(ret) 1300 1301 def GetMethodIDImpl(self, called_by_native): 1302 """Returns the implementation of GetMethodID.""" 1303 if self.options.eager_called_by_natives: 1304 template = Template("""\ 1305 env->Get${STATIC_METHOD_PART}MethodID( 1306 ${JAVA_CLASS}_clazz(env), 1307 "${JNI_NAME}", ${JNI_SIGNATURE});""") 1308 else: 1309 template = Template("""\ 1310 base::android::MethodID::LazyGet< 1311 base::android::MethodID::TYPE_${STATIC}>( 1312 env, ${JAVA_CLASS}_clazz(env), 1313 "${JNI_NAME}", 1314 ${JNI_SIGNATURE}, 1315 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); 1316 """) 1317 jni_name = called_by_native.name 1318 jni_return_type = called_by_native.return_type 1319 if called_by_native.is_constructor: 1320 jni_name = '<init>' 1321 jni_return_type = 'void' 1322 if called_by_native.signature: 1323 signature = called_by_native.signature 1324 else: 1325 signature = JniParams.Signature(called_by_native.params, 1326 jni_return_type, 1327 True) 1328 values = { 1329 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, 1330 'JNI_NAME': jni_name, 1331 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, 1332 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE', 1333 'STATIC_METHOD_PART': 'Static' if called_by_native.static else '', 1334 'JNI_SIGNATURE': signature, 1335 } 1336 return template.substitute(values) 1337 1338 1339 def WrapOutput(output): 1340 ret = [] 1341 for line in output.splitlines(): 1342 # Do not wrap lines under 80 characters or preprocessor directives. 1343 if len(line) < 80 or line.lstrip()[:1] == '#': 1344 stripped = line.rstrip() 1345 if len(ret) == 0 or len(ret[-1]) or len(stripped): 1346 ret.append(stripped) 1347 else: 1348 first_line_indent = ' ' * (len(line) - len(line.lstrip())) 1349 subsequent_indent = first_line_indent + ' ' * 4 1350 if line.startswith('//'): 1351 subsequent_indent = '//' + subsequent_indent 1352 wrapper = textwrap.TextWrapper(width=80, 1353 subsequent_indent=subsequent_indent, 1354 break_long_words=False) 1355 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)] 1356 ret += [''] 1357 return '\n'.join(ret) 1358 1359 1360 def ExtractJarInputFile(jar_file, input_file, out_dir): 1361 """Extracts input file from jar and returns the filename. 1362 1363 The input file is extracted to the same directory that the generated jni 1364 headers will be placed in. This is passed as an argument to script. 1365 1366 Args: 1367 jar_file: the jar file containing the input files to extract. 1368 input_files: the list of files to extract from the jar file. 1369 out_dir: the name of the directories to extract to. 1370 1371 Returns: 1372 the name of extracted input file. 1373 """ 1374 jar_file = zipfile.ZipFile(jar_file) 1375 1376 out_dir = os.path.join(out_dir, os.path.dirname(input_file)) 1377 try: 1378 os.makedirs(out_dir) 1379 except OSError as e: 1380 if e.errno != errno.EEXIST: 1381 raise 1382 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) 1383 with open(extracted_file_name, 'w') as outfile: 1384 outfile.write(jar_file.read(input_file)) 1385 1386 return extracted_file_name 1387 1388 1389 def GenerateJNIHeader(input_file, output_file, options): 1390 try: 1391 if os.path.splitext(input_file)[1] == '.class': 1392 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options) 1393 content = jni_from_javap.GetContent() 1394 else: 1395 jni_from_java_source = JNIFromJavaSource.CreateFromFile( 1396 input_file, options) 1397 content = jni_from_java_source.GetContent() 1398 except ParseError, e: 1399 print e 1400 sys.exit(1) 1401 if output_file: 1402 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))): 1403 os.makedirs(os.path.dirname(os.path.abspath(output_file))) 1404 if options.optimize_generation and os.path.exists(output_file): 1405 with file(output_file, 'r') as f: 1406 existing_content = f.read() 1407 if existing_content == content: 1408 return 1409 with file(output_file, 'w') as f: 1410 f.write(content) 1411 else: 1412 print output 1413 1414 1415 def GetScriptName(): 1416 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) 1417 base_index = 0 1418 for idx, value in enumerate(script_components): 1419 if value == 'base' or value == 'third_party': 1420 base_index = idx 1421 break 1422 return os.sep.join(script_components[base_index:]) 1423 1424 1425 def main(argv): 1426 usage = """usage: %prog [OPTIONS] 1427 This script will parse the given java source code extracting the native 1428 declarations and print the header file to stdout (or a file). 1429 See SampleForTests.java for more details. 1430 """ 1431 option_parser = optparse.OptionParser(usage=usage) 1432 option_parser.add_option('-j', '--jar_file', dest='jar_file', 1433 help='Extract the list of input files from' 1434 ' a specified jar file.' 1435 ' Uses javap to extract the methods from a' 1436 ' pre-compiled class. --input should point' 1437 ' to pre-compiled Java .class files.') 1438 option_parser.add_option('-n', dest='namespace', 1439 help='Uses as a namespace in the generated header ' 1440 'instead of the javap class name, or when there is ' 1441 'no JNINamespace annotation in the java source.') 1442 option_parser.add_option('--input_file', 1443 help='Single input file name. The output file name ' 1444 'will be derived from it. Must be used with ' 1445 '--output_dir.') 1446 option_parser.add_option('--output_dir', 1447 help='The output directory. Must be used with ' 1448 '--input') 1449 option_parser.add_option('--optimize_generation', type="int", 1450 default=0, help='Whether we should optimize JNI ' 1451 'generation by not regenerating files if they have ' 1452 'not changed.') 1453 option_parser.add_option('--jarjar', 1454 help='Path to optional jarjar rules file.') 1455 option_parser.add_option('--script_name', default=GetScriptName(), 1456 help='The name of this script in the generated ' 1457 'header.') 1458 option_parser.add_option('--includes', 1459 help='The comma-separated list of header files to ' 1460 'include in the generated header.') 1461 option_parser.add_option('--pure_native_methods', 1462 action='store_true', dest='pure_native_methods', 1463 help='When true, the native methods will be called ' 1464 'without any JNI-specific arguments.') 1465 option_parser.add_option('--ptr_type', default='int', 1466 type='choice', choices=['int', 'long'], 1467 help='The type used to represent native pointers in ' 1468 'Java code. For 32-bit, use int; ' 1469 'for 64-bit, use long.') 1470 option_parser.add_option('--jni_init_native_name', default='', 1471 help='The name of the JNI registration method that ' 1472 'is used to initialize all native methods. If a ' 1473 'method with this name is not present in the Java ' 1474 'source file, setting this option is a no-op. When ' 1475 'a method with this name is found however, the ' 1476 'naming convention Java_<packageName>_<className> ' 1477 'will limit the initialization to only the ' 1478 'top-level class.') 1479 option_parser.add_option('--eager_called_by_natives', 1480 action='store_true', dest='eager_called_by_natives', 1481 help='When true, the called-by-native methods will ' 1482 'be initialized in a non-atomic way.') 1483 option_parser.add_option('--cpp', default='cpp', 1484 help='The path to cpp command.') 1485 option_parser.add_option('--javap', default='javap', 1486 help='The path to javap command.') 1487 option_parser.add_option('--native_exports', action='store_true', 1488 help='Native method registration through .so ' 1489 'exports.') 1490 options, args = option_parser.parse_args(argv) 1491 if options.jar_file: 1492 input_file = ExtractJarInputFile(options.jar_file, options.input_file, 1493 options.output_dir) 1494 elif options.input_file: 1495 input_file = options.input_file 1496 else: 1497 option_parser.print_help() 1498 print '\nError: Must specify --jar_file or --input_file.' 1499 return 1 1500 output_file = None 1501 if options.output_dir: 1502 root_name = os.path.splitext(os.path.basename(input_file))[0] 1503 output_file = os.path.join(options.output_dir, root_name) + '_jni.h' 1504 if options.jarjar: 1505 with open(options.jarjar) as f: 1506 JniParams.SetJarJarMappings(f.read()) 1507 GenerateJNIHeader(input_file, output_file, options) 1508 1509 1510 if __name__ == '__main__': 1511 sys.exit(main(sys.argv)) 1512