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