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