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