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