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