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