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