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 == '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.is_constructor = kwargs.get('is_constructor', False) 80 self.env_call = GetEnvCall(self.is_constructor, self.static, 81 self.return_type) 82 self.static_cast = GetStaticCastForReturnType(self.return_type) 83 84 85 def JavaDataTypeToC(java_type): 86 """Returns a C datatype for the given java type.""" 87 java_pod_type_map = { 88 'int': 'jint', 89 'byte': 'jbyte', 90 'char': 'jchar', 91 'short': 'jshort', 92 'boolean': 'jboolean', 93 'long': 'jlong', 94 'double': 'jdouble', 95 'float': 'jfloat', 96 } 97 java_type_map = { 98 'void': 'void', 99 'String': 'jstring', 100 'java/lang/String': 'jstring', 101 'Class': 'jclass', 102 'java/lang/Class': 'jclass', 103 } 104 105 if java_type in java_pod_type_map: 106 return java_pod_type_map[java_type] 107 elif java_type in java_type_map: 108 return java_type_map[java_type] 109 elif java_type.endswith('[]'): 110 if java_type[:-2] in java_pod_type_map: 111 return java_pod_type_map[java_type[:-2]] + 'Array' 112 return 'jobjectArray' 113 else: 114 return 'jobject' 115 116 117 class JniParams(object): 118 _imports = [] 119 _fully_qualified_class = '' 120 _package = '' 121 _inner_classes = [] 122 _remappings = [] 123 124 @staticmethod 125 def SetFullyQualifiedClass(fully_qualified_class): 126 JniParams._fully_qualified_class = 'L' + fully_qualified_class 127 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1]) 128 129 @staticmethod 130 def ExtractImportsAndInnerClasses(contents): 131 contents = contents.replace('\n', '') 132 re_import = re.compile(r'import.*?(?P<class>\S*?);') 133 for match in re.finditer(re_import, contents): 134 JniParams._imports += ['L' + match.group('class').replace('.', '/')] 135 136 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W') 137 for match in re.finditer(re_inner, contents): 138 inner = match.group('name') 139 if not JniParams._fully_qualified_class.endswith(inner): 140 JniParams._inner_classes += [JniParams._fully_qualified_class + '$' + 141 inner] 142 143 @staticmethod 144 def JavaToJni(param): 145 """Converts a java param into a JNI signature type.""" 146 pod_param_map = { 147 'int': 'I', 148 'boolean': 'Z', 149 'char': 'C', 150 'short': 'S', 151 'long': 'J', 152 'double': 'D', 153 'float': 'F', 154 'byte': 'B', 155 'void': 'V', 156 } 157 object_param_list = [ 158 'Ljava/lang/Boolean', 159 'Ljava/lang/Integer', 160 'Ljava/lang/Long', 161 'Ljava/lang/Object', 162 'Ljava/lang/String', 163 'Ljava/lang/Class', 164 ] 165 prefix = '' 166 # Array? 167 while param[-2:] == '[]': 168 prefix += '[' 169 param = param[:-2] 170 # Generic? 171 if '<' in param: 172 param = param[:param.index('<')] 173 if param in pod_param_map: 174 return prefix + pod_param_map[param] 175 if '/' in param: 176 # Coming from javap, use the fully qualified param directly. 177 return prefix + 'L' + JniParams.RemapClassName(param) + ';' 178 for qualified_name in (object_param_list + 179 [JniParams._fully_qualified_class] + 180 JniParams._inner_classes): 181 if (qualified_name.endswith('/' + param) or 182 qualified_name.endswith('$' + param.replace('.', '$')) or 183 qualified_name == 'L' + param): 184 return prefix + JniParams.RemapClassName(qualified_name) + ';' 185 186 # Is it from an import? (e.g. referecing Class from import pkg.Class; 187 # note that referencing an inner class Inner from import pkg.Class.Inner 188 # is not supported). 189 for qualified_name in JniParams._imports: 190 if qualified_name.endswith('/' + param): 191 # Ensure it's not an inner class. 192 components = qualified_name.split('/') 193 if len(components) > 2 and components[-2][0].isupper(): 194 raise SyntaxError('Inner class (%s) can not be imported ' 195 'and used by JNI (%s). Please import the outer ' 196 'class and use Outer.Inner instead.' % 197 (qualified_name, param)) 198 return prefix + JniParams.RemapClassName(qualified_name) + ';' 199 200 # Is it an inner class from an outer class import? (e.g. referencing 201 # Class.Inner from import pkg.Class). 202 if '.' in param: 203 components = param.split('.') 204 outer = '/'.join(components[:-1]) 205 inner = components[-1] 206 for qualified_name in JniParams._imports: 207 if qualified_name.endswith('/' + outer): 208 return (prefix + JniParams.RemapClassName(qualified_name) + 209 '$' + inner + ';') 210 211 # Type not found, falling back to same package as this class. 212 return (prefix + 'L' + 213 JniParams.RemapClassName(JniParams._package + '/' + param) + ';') 214 215 @staticmethod 216 def Signature(params, returns, wrap): 217 """Returns the JNI signature for the given datatypes.""" 218 items = ['('] 219 items += [JniParams.JavaToJni(param.datatype) for param in params] 220 items += [')'] 221 items += [JniParams.JavaToJni(returns)] 222 if wrap: 223 return '\n' + '\n'.join(['"' + item + '"' for item in items]) 224 else: 225 return '"' + ''.join(items) + '"' 226 227 @staticmethod 228 def Parse(params): 229 """Parses the params into a list of Param objects.""" 230 if not params: 231 return [] 232 ret = [] 233 for p in [p.strip() for p in params.split(',')]: 234 items = p.split(' ') 235 if 'final' in items: 236 items.remove('final') 237 param = Param( 238 datatype=items[0], 239 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), 240 ) 241 ret += [param] 242 return ret 243 244 @staticmethod 245 def RemapClassName(class_name): 246 """Remaps class names using the jarjar mapping table.""" 247 for old, new in JniParams._remappings: 248 if old in class_name: 249 return class_name.replace(old, new, 1) 250 return class_name 251 252 @staticmethod 253 def SetJarJarMappings(mappings): 254 """Parse jarjar mappings from a string.""" 255 JniParams._remappings = [] 256 for line in mappings.splitlines(): 257 keyword, src, dest = line.split() 258 if keyword != 'rule': 259 continue 260 assert src.endswith('.**') 261 src = src[:-2].replace('.', '/') 262 dest = dest.replace('.', '/') 263 if dest.endswith('@0'): 264 JniParams._remappings.append((src, dest[:-2] + src)) 265 else: 266 assert dest.endswith('@1') 267 JniParams._remappings.append((src, dest[:-2])) 268 269 270 def ExtractJNINamespace(contents): 271 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') 272 m = re.findall(re_jni_namespace, contents) 273 if not m: 274 return '' 275 return m[0] 276 277 278 def ExtractFullyQualifiedJavaClassName(java_file_name, contents): 279 re_package = re.compile('.*?package (.*?);') 280 matches = re.findall(re_package, contents) 281 if not matches: 282 raise SyntaxError('Unable to find "package" line in %s' % java_file_name) 283 return (matches[0].replace('.', '/') + '/' + 284 os.path.splitext(os.path.basename(java_file_name))[0]) 285 286 287 def ExtractNatives(contents): 288 """Returns a list of dict containing information about a native method.""" 289 contents = contents.replace('\n', '') 290 natives = [] 291 re_native = re.compile(r'(@NativeClassQualifiedName' 292 '\(\"(?P<native_class_name>.*?)\"\))?\s*' 293 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\)))?\s*' 294 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*?native ' 295 '(?P<return_type>\S*?) ' 296 '(?P<name>\w+?)\((?P<params>.*?)\);') 297 for match in re.finditer(re_native, contents): 298 native = NativeMethod( 299 static='static' in match.group('qualifiers'), 300 java_class_name=match.group('java_class_name'), 301 native_class_name=match.group('native_class_name'), 302 return_type=match.group('return_type'), 303 name=match.group('name').replace('native', ''), 304 params=JniParams.Parse(match.group('params'))) 305 natives += [native] 306 return natives 307 308 309 def GetStaticCastForReturnType(return_type): 310 type_map = { 'String' : 'jstring', 311 'java/lang/String' : 'jstring', 312 'boolean[]': 'jbooleanArray', 313 'byte[]': 'jbyteArray', 314 'char[]': 'jcharArray', 315 'short[]': 'jshortArray', 316 'int[]': 'jintArray', 317 'long[]': 'jlongArray', 318 'double[]': 'jdoubleArray' } 319 ret = type_map.get(return_type, None) 320 if ret: 321 return ret 322 if return_type.endswith('[]'): 323 return 'jobjectArray' 324 return None 325 326 327 def GetEnvCall(is_constructor, is_static, return_type): 328 """Maps the types availabe via env->Call__Method.""" 329 if is_constructor: 330 return 'NewObject' 331 env_call_map = {'boolean': 'Boolean', 332 'byte': 'Byte', 333 'char': 'Char', 334 'short': 'Short', 335 'int': 'Int', 336 'long': 'Long', 337 'float': 'Float', 338 'void': 'Void', 339 'double': 'Double', 340 'Object': 'Object', 341 } 342 call = env_call_map.get(return_type, 'Object') 343 if is_static: 344 call = 'Static' + call 345 return 'Call' + call + 'Method' 346 347 348 def GetMangledParam(datatype): 349 """Returns a mangled identifier for the datatype.""" 350 if len(datatype) <= 2: 351 return datatype.replace('[', 'A') 352 ret = '' 353 for i in range(1, len(datatype)): 354 c = datatype[i] 355 if c == '[': 356 ret += 'A' 357 elif c.isupper() or datatype[i - 1] in ['/', 'L']: 358 ret += c.upper() 359 return ret 360 361 362 def GetMangledMethodName(name, params, return_type): 363 """Returns a mangled method name for the given signature. 364 365 The returned name can be used as a C identifier and will be unique for all 366 valid overloads of the same method. 367 368 Args: 369 name: string. 370 params: list of Param. 371 return_type: string. 372 373 Returns: 374 A mangled name. 375 """ 376 mangled_items = [] 377 for datatype in [return_type] + [x.datatype for x in params]: 378 mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))] 379 mangled_name = name + '_'.join(mangled_items) 380 assert re.match(r'[0-9a-zA-Z_]+', mangled_name) 381 return mangled_name 382 383 384 def MangleCalledByNatives(called_by_natives): 385 """Mangles all the overloads from the call_by_natives list.""" 386 method_counts = collections.defaultdict( 387 lambda: collections.defaultdict(lambda: 0)) 388 for called_by_native in called_by_natives: 389 java_class_name = called_by_native.java_class_name 390 name = called_by_native.name 391 method_counts[java_class_name][name] += 1 392 for called_by_native in called_by_natives: 393 java_class_name = called_by_native.java_class_name 394 method_name = called_by_native.name 395 method_id_var_name = method_name 396 if method_counts[java_class_name][method_name] > 1: 397 method_id_var_name = GetMangledMethodName(method_name, 398 called_by_native.params, 399 called_by_native.return_type) 400 called_by_native.method_id_var_name = method_id_var_name 401 return called_by_natives 402 403 404 # Regex to match the JNI return types that should be included in a 405 # ScopedJavaLocalRef. 406 RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array') 407 408 # Regex to match a string like "@CalledByNative public void foo(int bar)". 409 RE_CALLED_BY_NATIVE = re.compile( 410 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?' 411 '\s+(?P<prefix>[\w ]*?)' 412 '\s*(?P<return_type>\S+?)' 413 '\s+(?P<name>\w+)' 414 '\s*\((?P<params>[^\)]*)\)') 415 416 417 def ExtractCalledByNatives(contents): 418 """Parses all methods annotated with @CalledByNative. 419 420 Args: 421 contents: the contents of the java file. 422 423 Returns: 424 A list of dict with information about the annotated methods. 425 TODO(bulach): return a CalledByNative object. 426 427 Raises: 428 ParseError: if unable to parse. 429 """ 430 called_by_natives = [] 431 for match in re.finditer(RE_CALLED_BY_NATIVE, contents): 432 called_by_natives += [CalledByNative( 433 system_class=False, 434 unchecked='Unchecked' in match.group('Unchecked'), 435 static='static' in match.group('prefix'), 436 java_class_name=match.group('annotation') or '', 437 return_type=match.group('return_type'), 438 name=match.group('name'), 439 params=JniParams.Parse(match.group('params')))] 440 # Check for any @CalledByNative occurrences that weren't matched. 441 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') 442 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): 443 if '@CalledByNative' in line1: 444 raise ParseError('could not parse @CalledByNative method signature', 445 line1, line2) 446 return MangleCalledByNatives(called_by_natives) 447 448 449 class JNIFromJavaP(object): 450 """Uses 'javap' to parse a .class file and generate the JNI header file.""" 451 452 def __init__(self, contents, namespace): 453 self.contents = contents 454 self.namespace = namespace 455 self.fully_qualified_class = re.match( 456 '.*?(class|interface) (?P<class_name>.*?)( |{)', 457 contents[1]).group('class_name') 458 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') 459 JniParams.SetFullyQualifiedClass(self.fully_qualified_class) 460 self.java_class_name = self.fully_qualified_class.split('/')[-1] 461 if not self.namespace: 462 self.namespace = 'JNI_' + self.java_class_name 463 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)' 464 '\((?P<params>.*?)\)') 465 self.called_by_natives = [] 466 for content in contents[2:]: 467 match = re.match(re_method, content) 468 if not match: 469 continue 470 self.called_by_natives += [CalledByNative( 471 system_class=True, 472 unchecked=False, 473 static='static' in match.group('prefix'), 474 java_class_name='', 475 return_type=match.group('return_type').replace('.', '/'), 476 name=match.group('name'), 477 params=JniParams.Parse(match.group('params').replace('.', '/')))] 478 re_constructor = re.compile('.*? public ' + 479 self.fully_qualified_class.replace('/', '.') + 480 '\((?P<params>.*?)\)') 481 for content in contents[2:]: 482 match = re.match(re_constructor, content) 483 if not match: 484 continue 485 self.called_by_natives += [CalledByNative( 486 system_class=True, 487 unchecked=False, 488 static=False, 489 java_class_name='', 490 return_type=self.fully_qualified_class, 491 name='Constructor', 492 params=JniParams.Parse(match.group('params').replace('.', '/')), 493 is_constructor=True)] 494 self.called_by_natives = MangleCalledByNatives(self.called_by_natives) 495 self.inl_header_file_generator = InlHeaderFileGenerator( 496 self.namespace, self.fully_qualified_class, [], self.called_by_natives) 497 498 def GetContent(self): 499 return self.inl_header_file_generator.GetContent() 500 501 @staticmethod 502 def CreateFromClass(class_file, namespace): 503 class_name = os.path.splitext(os.path.basename(class_file))[0] 504 p = subprocess.Popen(args=['javap', class_name], 505 cwd=os.path.dirname(class_file), 506 stdout=subprocess.PIPE, 507 stderr=subprocess.PIPE) 508 stdout, _ = p.communicate() 509 jni_from_javap = JNIFromJavaP(stdout.split('\n'), namespace) 510 return jni_from_javap 511 512 513 class JNIFromJavaSource(object): 514 """Uses the given java source file to generate the JNI header file.""" 515 516 def __init__(self, contents, fully_qualified_class): 517 contents = self._RemoveComments(contents) 518 JniParams.SetFullyQualifiedClass(fully_qualified_class) 519 JniParams.ExtractImportsAndInnerClasses(contents) 520 jni_namespace = ExtractJNINamespace(contents) 521 natives = ExtractNatives(contents) 522 called_by_natives = ExtractCalledByNatives(contents) 523 if len(natives) == 0 and len(called_by_natives) == 0: 524 raise SyntaxError('Unable to find any JNI methods for %s.' % 525 fully_qualified_class) 526 inl_header_file_generator = InlHeaderFileGenerator( 527 jni_namespace, fully_qualified_class, natives, called_by_natives) 528 self.content = inl_header_file_generator.GetContent() 529 530 def _RemoveComments(self, contents): 531 # We need to support both inline and block comments, and we need to handle 532 # strings that contain '//' or '/*'. Rather than trying to do all that with 533 # regexps, we just pipe the contents through the C preprocessor. We tell cpp 534 # the file has already been preprocessed, so it just removes comments and 535 # doesn't try to parse #include, #pragma etc. 536 # 537 # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java 538 # parser. Maybe we could ditch JNIFromJavaSource and just always use 539 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. 540 # http://code.google.com/p/chromium/issues/detail?id=138941 541 p = subprocess.Popen(args=['cpp', '-fpreprocessed'], 542 stdin=subprocess.PIPE, 543 stdout=subprocess.PIPE, 544 stderr=subprocess.PIPE) 545 stdout, _ = p.communicate(contents) 546 return stdout 547 548 def GetContent(self): 549 return self.content 550 551 @staticmethod 552 def CreateFromFile(java_file_name): 553 contents = file(java_file_name).read() 554 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, 555 contents) 556 return JNIFromJavaSource(contents, fully_qualified_class) 557 558 559 class InlHeaderFileGenerator(object): 560 """Generates an inline header file for JNI integration.""" 561 562 def __init__(self, namespace, fully_qualified_class, natives, 563 called_by_natives): 564 self.namespace = namespace 565 self.fully_qualified_class = fully_qualified_class 566 self.class_name = self.fully_qualified_class.split('/')[-1] 567 self.natives = natives 568 self.called_by_natives = called_by_natives 569 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' 570 571 def GetContent(self): 572 """Returns the content of the JNI binding file.""" 573 template = Template("""\ 574 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 575 // Use of this source code is governed by a BSD-style license that can be 576 // found in the LICENSE file. 577 578 579 // This file is autogenerated by 580 // ${SCRIPT_NAME} 581 // For 582 // ${FULLY_QUALIFIED_CLASS} 583 584 #ifndef ${HEADER_GUARD} 585 #define ${HEADER_GUARD} 586 587 #include <jni.h> 588 589 #include "base/android/jni_android.h" 590 #include "base/android/scoped_java_ref.h" 591 #include "base/basictypes.h" 592 #include "base/logging.h" 593 594 using base::android::ScopedJavaLocalRef; 595 596 // Step 1: forward declarations. 597 namespace { 598 $CLASS_PATH_DEFINITIONS 599 } // namespace 600 601 $OPEN_NAMESPACE 602 $FORWARD_DECLARATIONS 603 604 // Step 2: method stubs. 605 $METHOD_STUBS 606 607 // Step 3: RegisterNatives. 608 609 static bool RegisterNativesImpl(JNIEnv* env) { 610 $REGISTER_NATIVES_IMPL 611 return true; 612 } 613 $CLOSE_NAMESPACE 614 #endif // ${HEADER_GUARD} 615 """) 616 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) 617 base_index = script_components.index('base') 618 script_name = os.sep.join(script_components[base_index:]) 619 values = { 620 'SCRIPT_NAME': script_name, 621 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, 622 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), 623 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(), 624 'METHOD_STUBS': self.GetMethodStubsString(), 625 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), 626 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString(), 627 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), 628 'HEADER_GUARD': self.header_guard, 629 } 630 return WrapOutput(template.substitute(values)) 631 632 def GetClassPathDefinitionsString(self): 633 ret = [] 634 ret += [self.GetClassPathDefinitions()] 635 return '\n'.join(ret) 636 637 def GetForwardDeclarationsString(self): 638 ret = [] 639 for native in self.natives: 640 if native.type != 'method': 641 ret += [self.GetForwardDeclaration(native)] 642 return '\n'.join(ret) 643 644 def GetMethodStubsString(self): 645 ret = [] 646 for native in self.natives: 647 if native.type == 'method': 648 ret += [self.GetNativeMethodStub(native)] 649 for called_by_native in self.called_by_natives: 650 ret += [self.GetCalledByNativeMethodStub(called_by_native)] 651 return '\n'.join(ret) 652 653 def GetKMethodsString(self, clazz): 654 ret = [] 655 for native in self.natives: 656 if (native.java_class_name == clazz or 657 (not native.java_class_name and clazz == self.class_name)): 658 ret += [self.GetKMethodArrayEntry(native)] 659 return '\n'.join(ret) 660 661 def GetRegisterNativesImplString(self): 662 """Returns the implementation for RegisterNatives.""" 663 template = Template("""\ 664 static const JNINativeMethod kMethods${JAVA_CLASS}[] = { 665 ${KMETHODS} 666 }; 667 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS}); 668 669 if (env->RegisterNatives(g_${JAVA_CLASS}_clazz, 670 kMethods${JAVA_CLASS}, 671 kMethods${JAVA_CLASS}Size) < 0) { 672 LOG(ERROR) << "RegisterNatives failed in " << __FILE__; 673 return false; 674 } 675 """) 676 ret = [self.GetFindClasses()] 677 all_classes = self.GetUniqueClasses(self.natives) 678 all_classes[self.class_name] = self.fully_qualified_class 679 for clazz in all_classes: 680 kmethods = self.GetKMethodsString(clazz) 681 if kmethods: 682 values = {'JAVA_CLASS': clazz, 683 'KMETHODS': kmethods} 684 ret += [template.substitute(values)] 685 if not ret: return '' 686 return '\n' + '\n'.join(ret) 687 688 def GetOpenNamespaceString(self): 689 if self.namespace: 690 all_namespaces = ['namespace %s {' % ns 691 for ns in self.namespace.split('::')] 692 return '\n'.join(all_namespaces) 693 return '' 694 695 def GetCloseNamespaceString(self): 696 if self.namespace: 697 all_namespaces = ['} // namespace %s' % ns 698 for ns in self.namespace.split('::')] 699 all_namespaces.reverse() 700 return '\n'.join(all_namespaces) + '\n' 701 return '' 702 703 def GetJNIFirstParam(self, native): 704 ret = [] 705 if native.type == 'method': 706 ret = ['jobject obj'] 707 elif native.type == 'function': 708 if native.static: 709 ret = ['jclass clazz'] 710 else: 711 ret = ['jobject obj'] 712 return ret 713 714 def GetParamsInDeclaration(self, native): 715 """Returns the params for the stub declaration. 716 717 Args: 718 native: the native dictionary describing the method. 719 720 Returns: 721 A string containing the params. 722 """ 723 return ',\n '.join(self.GetJNIFirstParam(native) + 724 [JavaDataTypeToC(param.datatype) + ' ' + 725 param.name 726 for param in native.params]) 727 728 def GetCalledByNativeParamsInDeclaration(self, called_by_native): 729 return ',\n '.join([JavaDataTypeToC(param.datatype) + ' ' + 730 param.name 731 for param in called_by_native.params]) 732 733 def GetForwardDeclaration(self, native): 734 template = Template(""" 735 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS}); 736 """) 737 values = {'RETURN': JavaDataTypeToC(native.return_type), 738 'NAME': native.name, 739 'PARAMS': self.GetParamsInDeclaration(native)} 740 return template.substitute(values) 741 742 def GetNativeMethodStub(self, native): 743 """Returns stubs for native methods.""" 744 template = Template("""\ 745 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) { 746 DCHECK(${PARAM0_NAME}) << "${NAME}"; 747 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); 748 return native->${NAME}(env, obj${PARAMS_IN_CALL})${POST_CALL}; 749 } 750 """) 751 params_for_call = ', '.join(p.name for p in native.params[1:]) 752 if params_for_call: 753 params_for_call = ', ' + params_for_call 754 755 return_type = JavaDataTypeToC(native.return_type) 756 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): 757 scoped_return_type = 'ScopedJavaLocalRef<' + return_type + '>' 758 post_call = '.Release()' 759 else: 760 scoped_return_type = return_type 761 post_call = '' 762 values = { 763 'RETURN': return_type, 764 'SCOPED_RETURN': scoped_return_type, 765 'NAME': native.name, 766 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native), 767 'PARAM0_NAME': native.params[0].name, 768 'P0_TYPE': native.p0_type, 769 'PARAMS_IN_CALL': params_for_call, 770 'POST_CALL': post_call 771 } 772 return template.substitute(values) 773 774 def GetCalledByNativeMethodStub(self, called_by_native): 775 """Returns a string.""" 776 function_signature_template = Template("""\ 777 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\ 778 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") 779 function_header_template = Template("""\ 780 ${FUNCTION_SIGNATURE} {""") 781 function_header_with_unused_template = Template("""\ 782 ${FUNCTION_SIGNATURE} __attribute__ ((unused)); 783 ${FUNCTION_SIGNATURE} {""") 784 template = Template(""" 785 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0; 786 ${FUNCTION_HEADER} 787 /* Must call RegisterNativesImpl() */ 788 DCHECK(g_${JAVA_CLASS}_clazz); 789 jmethodID method_id = 790 ${GET_METHOD_ID_IMPL} 791 ${RETURN_DECLARATION} 792 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, 793 method_id${PARAMS_IN_CALL})${POST_CALL}; 794 ${CHECK_EXCEPTION} 795 ${RETURN_CLAUSE} 796 }""") 797 if called_by_native.static or called_by_native.is_constructor: 798 first_param_in_declaration = '' 799 first_param_in_call = ('g_%s_clazz' % 800 (called_by_native.java_class_name or 801 self.class_name)) 802 else: 803 first_param_in_declaration = ', jobject obj' 804 first_param_in_call = 'obj' 805 params_in_declaration = self.GetCalledByNativeParamsInDeclaration( 806 called_by_native) 807 if params_in_declaration: 808 params_in_declaration = ', ' + params_in_declaration 809 params_for_call = ', '.join(param.name 810 for param in called_by_native.params) 811 if params_for_call: 812 params_for_call = ', ' + params_for_call 813 pre_call = '' 814 post_call = '' 815 if called_by_native.static_cast: 816 pre_call = 'static_cast<%s>(' % called_by_native.static_cast 817 post_call = ')' 818 check_exception = '' 819 if not called_by_native.unchecked: 820 check_exception = 'base::android::CheckException(env);' 821 return_type = JavaDataTypeToC(called_by_native.return_type) 822 return_declaration = '' 823 return_clause = '' 824 if return_type != 'void': 825 pre_call = ' ' + pre_call 826 return_declaration = return_type + ' ret =' 827 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): 828 return_type = 'ScopedJavaLocalRef<' + return_type + '>' 829 return_clause = 'return ' + return_type + '(env, ret);' 830 else: 831 return_clause = 'return ret;' 832 values = { 833 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, 834 'METHOD': called_by_native.name, 835 'RETURN_TYPE': return_type, 836 'RETURN_DECLARATION': return_declaration, 837 'RETURN_CLAUSE': return_clause, 838 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, 839 'PARAMS_IN_DECLARATION': params_in_declaration, 840 'STATIC': 'Static' if called_by_native.static else '', 841 'PRE_CALL': pre_call, 842 'POST_CALL': post_call, 843 'ENV_CALL': called_by_native.env_call, 844 'FIRST_PARAM_IN_CALL': first_param_in_call, 845 'PARAMS_IN_CALL': params_for_call, 846 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, 847 'CHECK_EXCEPTION': check_exception, 848 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native) 849 } 850 values['FUNCTION_SIGNATURE'] = ( 851 function_signature_template.substitute(values)) 852 if called_by_native.system_class: 853 values['FUNCTION_HEADER'] = ( 854 function_header_with_unused_template.substitute(values)) 855 else: 856 values['FUNCTION_HEADER'] = function_header_template.substitute(values) 857 return template.substitute(values) 858 859 def GetKMethodArrayEntry(self, native): 860 template = Template("""\ 861 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""") 862 values = {'NAME': native.name, 863 'JNI_SIGNATURE': JniParams.Signature(native.params, 864 native.return_type, 865 True)} 866 return template.substitute(values) 867 868 def GetUniqueClasses(self, origin): 869 ret = {self.class_name: self.fully_qualified_class} 870 for entry in origin: 871 class_name = self.class_name 872 jni_class_path = self.fully_qualified_class 873 if entry.java_class_name: 874 class_name = entry.java_class_name 875 jni_class_path = self.fully_qualified_class + '$' + class_name 876 ret[class_name] = jni_class_path 877 return ret 878 879 def GetClassPathDefinitions(self): 880 """Returns the ClassPath constants.""" 881 ret = [] 882 template = Template("""\ 883 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""") 884 native_classes = self.GetUniqueClasses(self.natives) 885 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives) 886 all_classes = native_classes 887 all_classes.update(called_by_native_classes) 888 for clazz in all_classes: 889 values = { 890 'JAVA_CLASS': clazz, 891 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]), 892 } 893 ret += [template.substitute(values)] 894 ret += '' 895 for clazz in called_by_native_classes: 896 template = Template("""\ 897 // Leaking this jclass as we cannot use LazyInstance from some threads. 898 jclass g_${JAVA_CLASS}_clazz = NULL;""") 899 values = { 900 'JAVA_CLASS': clazz, 901 } 902 ret += [template.substitute(values)] 903 return '\n'.join(ret) 904 905 def GetFindClasses(self): 906 """Returns the imlementation of FindClass for all known classes.""" 907 template = Template("""\ 908 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( 909 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""") 910 ret = [] 911 for clazz in self.GetUniqueClasses(self.called_by_natives): 912 values = {'JAVA_CLASS': clazz} 913 ret += [template.substitute(values)] 914 return '\n'.join(ret) 915 916 def GetMethodIDImpl(self, called_by_native): 917 """Returns the implementation of GetMethodID.""" 918 template = Template("""\ 919 base::android::MethodID::LazyGet< 920 base::android::MethodID::TYPE_${STATIC}>( 921 env, g_${JAVA_CLASS}_clazz, 922 "${JNI_NAME}", 923 ${JNI_SIGNATURE}, 924 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); 925 """) 926 jni_name = called_by_native.name 927 jni_return_type = called_by_native.return_type 928 if called_by_native.is_constructor: 929 jni_name = '<init>' 930 jni_return_type = 'void' 931 values = { 932 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, 933 'JNI_NAME': jni_name, 934 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, 935 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE', 936 'JNI_SIGNATURE': JniParams.Signature(called_by_native.params, 937 jni_return_type, 938 True) 939 } 940 return template.substitute(values) 941 942 943 def WrapOutput(output): 944 ret = [] 945 for line in output.splitlines(): 946 # Do not wrap lines under 80 characters or preprocessor directives. 947 if len(line) < 80 or line.lstrip()[:1] == '#': 948 stripped = line.rstrip() 949 if len(ret) == 0 or len(ret[-1]) or len(stripped): 950 ret.append(stripped) 951 else: 952 first_line_indent = ' ' * (len(line) - len(line.lstrip())) 953 subsequent_indent = first_line_indent + ' ' * 4 954 if line.startswith('//'): 955 subsequent_indent = '//' + subsequent_indent 956 wrapper = textwrap.TextWrapper(width=80, 957 subsequent_indent=subsequent_indent, 958 break_long_words=False) 959 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)] 960 ret += [''] 961 return '\n'.join(ret) 962 963 964 def ExtractJarInputFile(jar_file, input_file, out_dir): 965 """Extracts input file from jar and returns the filename. 966 967 The input file is extracted to the same directory that the generated jni 968 headers will be placed in. This is passed as an argument to script. 969 970 Args: 971 jar_file: the jar file containing the input files to extract. 972 input_files: the list of files to extract from the jar file. 973 out_dir: the name of the directories to extract to. 974 975 Returns: 976 the name of extracted input file. 977 """ 978 jar_file = zipfile.ZipFile(jar_file) 979 980 out_dir = os.path.join(out_dir, os.path.dirname(input_file)) 981 try: 982 os.makedirs(out_dir) 983 except OSError as e: 984 if e.errno != errno.EEXIST: 985 raise 986 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) 987 with open(extracted_file_name, 'w') as outfile: 988 outfile.write(jar_file.read(input_file)) 989 990 return extracted_file_name 991 992 993 def GenerateJNIHeader(input_file, output_file, namespace, skip_if_same): 994 try: 995 if os.path.splitext(input_file)[1] == '.class': 996 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, namespace) 997 content = jni_from_javap.GetContent() 998 else: 999 jni_from_java_source = JNIFromJavaSource.CreateFromFile(input_file) 1000 content = jni_from_java_source.GetContent() 1001 except ParseError, e: 1002 print e 1003 sys.exit(1) 1004 if output_file: 1005 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))): 1006 os.makedirs(os.path.dirname(os.path.abspath(output_file))) 1007 if skip_if_same and os.path.exists(output_file): 1008 with file(output_file, 'r') as f: 1009 existing_content = f.read() 1010 if existing_content == content: 1011 return 1012 with file(output_file, 'w') as f: 1013 f.write(content) 1014 else: 1015 print output 1016 1017 1018 def main(argv): 1019 usage = """usage: %prog [OPTIONS] 1020 This script will parse the given java source code extracting the native 1021 declarations and print the header file to stdout (or a file). 1022 See SampleForTests.java for more details. 1023 """ 1024 option_parser = optparse.OptionParser(usage=usage) 1025 option_parser.add_option('-j', dest='jar_file', 1026 help='Extract the list of input files from' 1027 ' a specified jar file.' 1028 ' Uses javap to extract the methods from a' 1029 ' pre-compiled class. --input should point' 1030 ' to pre-compiled Java .class files.') 1031 option_parser.add_option('-n', dest='namespace', 1032 help='Uses as a namespace in the generated header,' 1033 ' instead of the javap class name.') 1034 option_parser.add_option('--input_file', 1035 help='Single input file name. The output file name ' 1036 'will be derived from it. Must be used with ' 1037 '--output_dir.') 1038 option_parser.add_option('--output_dir', 1039 help='The output directory. Must be used with ' 1040 '--input') 1041 option_parser.add_option('--optimize_generation', type="int", 1042 default=0, help='Whether we should optimize JNI ' 1043 'generation by not regenerating files if they have ' 1044 'not changed.') 1045 option_parser.add_option('--jarjar', 1046 help='Path to optional jarjar rules file.') 1047 options, args = option_parser.parse_args(argv) 1048 if options.jar_file: 1049 input_file = ExtractJarInputFile(options.jar_file, options.input_file, 1050 options.output_dir) 1051 else: 1052 input_file = options.input_file 1053 output_file = None 1054 if options.output_dir: 1055 root_name = os.path.splitext(os.path.basename(input_file))[0] 1056 output_file = os.path.join(options.output_dir, root_name) + '_jni.h' 1057 if options.jarjar: 1058 with open(options.jarjar) as f: 1059 JniParams.SetJarJarMappings(f.read()) 1060 GenerateJNIHeader(input_file, output_file, options.namespace, 1061 options.optimize_generation) 1062 1063 1064 if __name__ == '__main__': 1065 sys.exit(main(sys.argv)) 1066