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