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