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