Home | History | Annotate | Download | only in jni_generator
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Extracts native methods from a Java file and generates the JNI bindings.
      7 If you change this, please run and update the tests."""
      8 
      9 import collections
     10 import errno
     11 import optparse
     12 import os
     13 import re
     14 from string import Template
     15 import subprocess
     16 import sys
     17 import textwrap
     18 import zipfile
     19 
     20 CHROMIUM_SRC = os.path.join(
     21     os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
     22 BUILD_ANDROID_GYP = os.path.join(
     23     CHROMIUM_SRC, 'build', 'android', 'gyp')
     24 
     25 sys.path.append(BUILD_ANDROID_GYP)
     26 
     27 from 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 
     59 class 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 
     72 class 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 
     80 class 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 
    104 class 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 
    123 class ConstantField(object):
    124   def __init__(self, **kwargs):
    125     self.name = kwargs['name']
    126     self.value = kwargs['value']
    127 
    128 
    129 def 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 
    164 def 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 
    172 def _JavaDataTypeToCForDeclaration(java_type):
    173   """Returns a JavaRef-wrapped C datatype for the given java type."""
    174   return WrapCTypeForDeclaration(JavaDataTypeToC(java_type))
    175 
    176 
    177 def 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 
    189 def 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 
    205 def _GetJNIFirstParamType(native):
    206   if native.type == 'function' and native.static:
    207     return 'jclass'
    208   return 'jobject'
    209 
    210 
    211 def _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 
    218 def _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 
    233 def 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 
    246 def _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 
    265 class 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 
    447 def 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 
    455 def 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 
    464 def 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 
    481 def 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 
    492 def 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 
    498 def GetRegistrationFunctionName(fully_qualified_class):
    499   """Returns the register name with a given class."""
    500   return 'RegisterNative_' + GetBinaryClassName(fully_qualified_class)
    501 
    502 
    503 def 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 
    527 def 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 
    548 def 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 
    562 def 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 
    585 def 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.
    606 RE_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)".
    610 RE_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).
    622 def RemoveIndentedEmptyLines(string):
    623   return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE)
    624 
    625 
    626 def 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 
    669 def 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 
    687 class 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 
    779 class 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 
    808 class 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("""
    850 extern const char kClassPath_${JAVA_CLASS}[];
    851 """)
    852     else:
    853       template = Template("""
    854 JNI_REGISTRATION_EXPORT extern const char kClassPath_${JAVA_CLASS}[];
    855 const 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
    869 inline 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("""\
    877 extern 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.
    882 JNI_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 
    894 class 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("""\
   1090 JNI_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("""\
   1105 static ${RETURN_DECLARATION} ${IMPL_METHOD_NAME}(JNIEnv* env, ${PARAMS});
   1106 
   1107 JNI_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("""\
   1213 static ${RETURN_TYPE} Java_${JAVA_CLASS_ONLY}_${METHOD_ID_VAR_NAME}(\
   1214 JNIEnv* 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("""
   1221 static 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 
   1260 def 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 
   1276 def 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 
   1305 def 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 
   1323 def 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 
   1333 def 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 
   1343 def main(argv):
   1344   usage = """usage: %prog [OPTIONS]
   1345 This script will parse the given java source code extracting the native
   1346 declarations and print the header file to stdout (or a file).
   1347 See 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 
   1408 if __name__ == '__main__':
   1409   sys.exit(main(sys.argv))
   1410