• 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
21
22class 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
35class 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
43class 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
67class 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
86class ConstantField(object):
87  def __init__(self, **kwargs):
88    self.name = kwargs['name']
89    self.value = kwargs['value']
90
91
92def 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
127def 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
135def 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
151class 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
374def 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
382def 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
391def 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
414def 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
433def 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
454def 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
468def 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
490def 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.
512RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array')
513
514# Regex to match a string like "@CalledByNative public void foo(int bar)".
515RE_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
523def 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
555class 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
647class 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
700class 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.
745namespace {
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("""\
793jmethodID 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("""\
882static 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("""\
957extern "C" JNIEXPORT bool JNICALL
958Java_${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 = """
1027static ${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 = """\
1063static ${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("""
1173static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\
1174JNIEnv* 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("""\
1185static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
1186JNIEnv* 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("""
1193static 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("""\
1240const 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.
1261base::subtle::AtomicWord g_${JAVA_CLASS}_clazz = 0;
1262#define ${JAVA_CLASS}_clazz(env) \
1263base::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.
1268jclass 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("""\
1305env->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
1339def 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
1360def 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
1389def 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
1415def 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
1425def main(argv):
1426  usage = """usage: %prog [OPTIONS]
1427This script will parse the given java source code extracting the native
1428declarations and print the header file to stdout (or a file).
1429See 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
1510if __name__ == '__main__':
1511  sys.exit(main(sys.argv))
1512