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