#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Extracts native methods from a Java file and generates the JNI bindings.
If you change this, please run and update the tests."""

import collections
import errno
import optparse
import os
import re
import string
from string import Template
import subprocess
import sys
import textwrap
import zipfile


class ParseError(Exception):
  """Exception thrown when we can't parse the input file."""

  def __init__(self, description, *context_lines):
    Exception.__init__(self)
    self.description = description
    self.context_lines = context_lines

  def __str__(self):
    context = '\n'.join(self.context_lines)
    return '***\nERROR: %s\n\n%s\n***' % (self.description, context)


class Param(object):
  """Describes a param for a method, either java or native."""

  def __init__(self, **kwargs):
    self.datatype = kwargs['datatype']
    self.name = kwargs['name']


class NativeMethod(object):
  """Describes a C/C++ method that is called by Java code"""

  def __init__(self, **kwargs):
    self.static = kwargs['static']
    self.java_class_name = kwargs['java_class_name']
    self.return_type = kwargs['return_type']
    self.name = kwargs['name']
    self.params = kwargs['params']
    if self.params:
      assert type(self.params) is list
      assert type(self.params[0]) is Param
    if (self.params and
        self.params[0].datatype == kwargs.get('ptr_type', 'int') and
        self.params[0].name.startswith('native')):
      self.type = 'method'
      self.p0_type = self.params[0].name[len('native'):]
      if kwargs.get('native_class_name'):
        self.p0_type = kwargs['native_class_name']
    else:
      self.type = 'function'
    self.method_id_var_name = kwargs.get('method_id_var_name', None)


class CalledByNative(object):
  """Describes a java method exported to c/c++"""

  def __init__(self, **kwargs):
    self.system_class = kwargs['system_class']
    self.unchecked = kwargs['unchecked']
    self.static = kwargs['static']
    self.java_class_name = kwargs['java_class_name']
    self.return_type = kwargs['return_type']
    self.name = kwargs['name']
    self.params = kwargs['params']
    self.method_id_var_name = kwargs.get('method_id_var_name', None)
    self.signature = kwargs.get('signature')
    self.is_constructor = kwargs.get('is_constructor', False)
    self.env_call = GetEnvCall(self.is_constructor, self.static,
                               self.return_type)
    self.static_cast = GetStaticCastForReturnType(self.return_type)


class ConstantField(object):
  def __init__(self, **kwargs):
    self.name = kwargs['name']
    self.value = kwargs['value']


def JavaDataTypeToC(java_type):
  """Returns a C datatype for the given java type."""
  java_pod_type_map = {
      'int': 'jint',
      'byte': 'jbyte',
      'char': 'jchar',
      'short': 'jshort',
      'boolean': 'jboolean',
      'long': 'jlong',
      'double': 'jdouble',
      'float': 'jfloat',
  }
  java_type_map = {
      'void': 'void',
      'String': 'jstring',
      'java/lang/String': 'jstring',
      'java/lang/Class': 'jclass',
  }

  if java_type in java_pod_type_map:
    return java_pod_type_map[java_type]
  elif java_type in java_type_map:
    return java_type_map[java_type]
  elif java_type.endswith('[]'):
    if java_type[:-2] in java_pod_type_map:
      return java_pod_type_map[java_type[:-2]] + 'Array'
    return 'jobjectArray'
  elif java_type.startswith('Class'):
    # Checking just the start of the name, rather than a direct comparison,
    # in order to handle generics.
    return 'jclass'
  else:
    return 'jobject'


def JavaDataTypeToCForCalledByNativeParam(java_type):
  """Returns a C datatype to be when calling from native."""
  if java_type == 'int':
    return 'JniIntWrapper'
  else:
    return JavaDataTypeToC(java_type)


def JavaReturnValueToC(java_type):
  """Returns a valid C return value for the given java type."""
  java_pod_type_map = {
      'int': '0',
      'byte': '0',
      'char': '0',
      'short': '0',
      'boolean': 'false',
      'long': '0',
      'double': '0',
      'float': '0',
      'void': ''
  }
  return java_pod_type_map.get(java_type, 'NULL')


class JniParams(object):
  _imports = []
  _fully_qualified_class = ''
  _package = ''
  _inner_classes = []
  _remappings = []
  _implicit_imports = []

  @staticmethod
  def SetFullyQualifiedClass(fully_qualified_class):
    JniParams._fully_qualified_class = 'L' + fully_qualified_class
    JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1])

  @staticmethod
  def AddAdditionalImport(class_name):
    assert class_name.endswith('.class')
    raw_class_name = class_name[:-len('.class')]
    if '.' in raw_class_name:
      raise SyntaxError('%s cannot be used in @JNIAdditionalImport. '
                        'Only import unqualified outer classes.' % class_name)
    new_import = 'L%s/%s' % (JniParams._package, raw_class_name)
    if new_import in JniParams._imports:
      raise SyntaxError('Do not use JNIAdditionalImport on an already '
                        'imported class: %s' % (new_import.replace('/', '.')))
    JniParams._imports += [new_import]

  @staticmethod
  def ExtractImportsAndInnerClasses(contents):
    if not JniParams._package:
      raise RuntimeError('SetFullyQualifiedClass must be called before '
                         'ExtractImportsAndInnerClasses')
    contents = contents.replace('\n', '')
    re_import = re.compile(r'import.*?(?P<class>\S*?);')
    for match in re.finditer(re_import, contents):
      JniParams._imports += ['L' + match.group('class').replace('.', '/')]

    re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W')
    for match in re.finditer(re_inner, contents):
      inner = match.group('name')
      if not JniParams._fully_qualified_class.endswith(inner):
        JniParams._inner_classes += [JniParams._fully_qualified_class + '$' +
                                     inner]

    re_additional_imports = re.compile(
        r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
    for match in re.finditer(re_additional_imports, contents):
      for class_name in match.group('class_names').split(','):
        JniParams.AddAdditionalImport(class_name.strip())

  @staticmethod
  def ParseJavaPSignature(signature_line):
    prefix = 'Signature: '
    return '"%s"' % signature_line[signature_line.index(prefix) + len(prefix):]

  @staticmethod
  def JavaToJni(param):
    """Converts a java param into a JNI signature type."""
    pod_param_map = {
        'int': 'I',
        'boolean': 'Z',
        'char': 'C',
        'short': 'S',
        'long': 'J',
        'double': 'D',
        'float': 'F',
        'byte': 'B',
        'void': 'V',
    }
    object_param_list = [
        'Ljava/lang/Boolean',
        'Ljava/lang/Integer',
        'Ljava/lang/Long',
        'Ljava/lang/Object',
        'Ljava/lang/String',
        'Ljava/lang/Class',
    ]

    prefix = ''
    # Array?
    while param[-2:] == '[]':
      prefix += '['
      param = param[:-2]
    # Generic?
    if '<' in param:
      param = param[:param.index('<')]
    if param in pod_param_map:
      return prefix + pod_param_map[param]
    if '/' in param:
      # Coming from javap, use the fully qualified param directly.
      return prefix + 'L' + JniParams.RemapClassName(param) + ';'

    for qualified_name in (object_param_list +
                           [JniParams._fully_qualified_class] +
                           JniParams._inner_classes):
      if (qualified_name.endswith('/' + param) or
          qualified_name.endswith('$' + param.replace('.', '$')) or
          qualified_name == 'L' + param):
        return prefix + JniParams.RemapClassName(qualified_name) + ';'

    # Is it from an import? (e.g. referecing Class from import pkg.Class;
    # note that referencing an inner class Inner from import pkg.Class.Inner
    # is not supported).
    for qualified_name in JniParams._imports:
      if qualified_name.endswith('/' + param):
        # Ensure it's not an inner class.
        components = qualified_name.split('/')
        if len(components) > 2 and components[-2][0].isupper():
          raise SyntaxError('Inner class (%s) can not be imported '
                            'and used by JNI (%s). Please import the outer '
                            'class and use Outer.Inner instead.' %
                            (qualified_name, param))
        return prefix + JniParams.RemapClassName(qualified_name) + ';'

    # Is it an inner class from an outer class import? (e.g. referencing
    # Class.Inner from import pkg.Class).
    if '.' in param:
      components = param.split('.')
      outer = '/'.join(components[:-1])
      inner = components[-1]
      for qualified_name in JniParams._imports:
        if qualified_name.endswith('/' + outer):
          return (prefix + JniParams.RemapClassName(qualified_name) +
                  '$' + inner + ';')
      raise SyntaxError('Inner class (%s) can not be '
                        'used directly by JNI. Please import the outer '
                        'class, probably:\n'
                        'import %s.%s;' %
                        (param, JniParams._package.replace('/', '.'),
                         outer.replace('/', '.')))

    JniParams._CheckImplicitImports(param)

    # Type not found, falling back to same package as this class.
    return (prefix + 'L' +
            JniParams.RemapClassName(JniParams._package + '/' + param) + ';')

  @staticmethod
  def _CheckImplicitImports(param):
    # Ensure implicit imports, such as java.lang.*, are not being treated
    # as being in the same package.
    if not JniParams._implicit_imports:
      # This file was generated from android.jar and lists
      # all classes that are implicitly imported.
      with file(os.path.join(os.path.dirname(sys.argv[0]),
                             'android_jar.classes'), 'r') as f:
        JniParams._implicit_imports = f.readlines()
    for implicit_import in JniParams._implicit_imports:
      implicit_import = implicit_import.strip().replace('.class', '')
      implicit_import = implicit_import.replace('/', '.')
      if implicit_import.endswith('.' + param):
        raise SyntaxError('Ambiguous class (%s) can not be used directly '
                          'by JNI.\nPlease import it, probably:\n\n'
                          'import %s;' %
                          (param, implicit_import))


  @staticmethod
  def Signature(params, returns, wrap):
    """Returns the JNI signature for the given datatypes."""
    items = ['(']
    items += [JniParams.JavaToJni(param.datatype) for param in params]
    items += [')']
    items += [JniParams.JavaToJni(returns)]
    if wrap:
      return '\n' + '\n'.join(['"' + item + '"' for item in items])
    else:
      return '"' + ''.join(items) + '"'

  @staticmethod
  def Parse(params):
    """Parses the params into a list of Param objects."""
    if not params:
      return []
    ret = []
    for p in [p.strip() for p in params.split(',')]:
      items = p.split(' ')
      if 'final' in items:
        items.remove('final')
      param = Param(
          datatype=items[0],
          name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
      )
      ret += [param]
    return ret

  @staticmethod
  def RemapClassName(class_name):
    """Remaps class names using the jarjar mapping table."""
    for old, new in JniParams._remappings:
      if old.endswith('**') and old[:-2] in class_name:
        return class_name.replace(old[:-2], new, 1)
      if '*' not in old and class_name.endswith(old):
        return class_name.replace(old, new, 1)

    return class_name

  @staticmethod
  def SetJarJarMappings(mappings):
    """Parse jarjar mappings from a string."""
    JniParams._remappings = []
    for line in mappings.splitlines():
      rule = line.split()
      if rule[0] != 'rule':
        continue
      _, src, dest = rule
      src = src.replace('.', '/')
      dest = dest.replace('.', '/')
      if src.endswith('**'):
        src_real_name = src[:-2]
      else:
        assert not '*' in src
        src_real_name = src

      if dest.endswith('@0'):
        JniParams._remappings.append((src, dest[:-2] + src_real_name))
      elif dest.endswith('@1'):
        assert '**' in src
        JniParams._remappings.append((src, dest[:-2]))
      else:
        assert not '@' in dest
        JniParams._remappings.append((src, dest))


def ExtractJNINamespace(contents):
  re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
  m = re.findall(re_jni_namespace, contents)
  if not m:
    return ''
  return m[0]


def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
  re_package = re.compile('.*?package (.*?);')
  matches = re.findall(re_package, contents)
  if not matches:
    raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
  return (matches[0].replace('.', '/') + '/' +
          os.path.splitext(os.path.basename(java_file_name))[0])


def ExtractNatives(contents, ptr_type):
  """Returns a list of dict containing information about a native method."""
  contents = contents.replace('\n', '')
  natives = []
  re_native = re.compile(r'(@NativeClassQualifiedName'
                         '\(\"(?P<native_class_name>.*?)\"\)\s+)?'
                         '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
                         '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
                         '(?P<return_type>\S*) '
                         '(?P<name>native\w+)\((?P<params>.*?)\);')
  for match in re.finditer(re_native, contents):
    native = NativeMethod(
        static='static' in match.group('qualifiers'),
        java_class_name=match.group('java_class_name'),
        native_class_name=match.group('native_class_name'),
        return_type=match.group('return_type'),
        name=match.group('name').replace('native', ''),
        params=JniParams.Parse(match.group('params')),
        ptr_type=ptr_type)
    natives += [native]
  return natives


def GetStaticCastForReturnType(return_type):
  type_map = { 'String' : 'jstring',
               'java/lang/String' : 'jstring',
               'boolean[]': 'jbooleanArray',
               'byte[]': 'jbyteArray',
               'char[]': 'jcharArray',
               'short[]': 'jshortArray',
               'int[]': 'jintArray',
               'long[]': 'jlongArray',
               'float[]': 'jfloatArray',
               'double[]': 'jdoubleArray' }
  ret = type_map.get(return_type, None)
  if ret:
    return ret
  if return_type.endswith('[]'):
    return 'jobjectArray'
  return None


def GetEnvCall(is_constructor, is_static, return_type):
  """Maps the types availabe via env->Call__Method."""
  if is_constructor:
    return 'NewObject'
  env_call_map = {'boolean': 'Boolean',
                  'byte': 'Byte',
                  'char': 'Char',
                  'short': 'Short',
                  'int': 'Int',
                  'long': 'Long',
                  'float': 'Float',
                  'void': 'Void',
                  'double': 'Double',
                  'Object': 'Object',
                 }
  call = env_call_map.get(return_type, 'Object')
  if is_static:
    call = 'Static' + call
  return 'Call' + call + 'Method'


def GetMangledParam(datatype):
  """Returns a mangled identifier for the datatype."""
  if len(datatype) <= 2:
    return datatype.replace('[', 'A')
  ret = ''
  for i in range(1, len(datatype)):
    c = datatype[i]
    if c == '[':
      ret += 'A'
    elif c.isupper() or datatype[i - 1] in ['/', 'L']:
      ret += c.upper()
  return ret


def GetMangledMethodName(name, params, return_type):
  """Returns a mangled method name for the given signature.

     The returned name can be used as a C identifier and will be unique for all
     valid overloads of the same method.

  Args:
     name: string.
     params: list of Param.
     return_type: string.

  Returns:
      A mangled name.
  """
  mangled_items = []
  for datatype in [return_type] + [x.datatype for x in params]:
    mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))]
  mangled_name = name + '_'.join(mangled_items)
  assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
  return mangled_name


def MangleCalledByNatives(called_by_natives):
  """Mangles all the overloads from the call_by_natives list."""
  method_counts = collections.defaultdict(
      lambda: collections.defaultdict(lambda: 0))
  for called_by_native in called_by_natives:
    java_class_name = called_by_native.java_class_name
    name = called_by_native.name
    method_counts[java_class_name][name] += 1
  for called_by_native in called_by_natives:
    java_class_name = called_by_native.java_class_name
    method_name = called_by_native.name
    method_id_var_name = method_name
    if method_counts[java_class_name][method_name] > 1:
      method_id_var_name = GetMangledMethodName(method_name,
                                                called_by_native.params,
                                                called_by_native.return_type)
    called_by_native.method_id_var_name = method_id_var_name
  return called_by_natives


# Regex to match the JNI return types that should be included in a
# ScopedJavaLocalRef.
RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array')

# Regex to match a string like "@CalledByNative public void foo(int bar)".
RE_CALLED_BY_NATIVE = re.compile(
    '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
    '\s+(?P<prefix>[\w ]*?)'
    '\s*(?P<return_type>\S+?)'
    '\s+(?P<name>\w+)'
    '\s*\((?P<params>[^\)]*)\)')


def ExtractCalledByNatives(contents):
  """Parses all methods annotated with @CalledByNative.

  Args:
    contents: the contents of the java file.

  Returns:
    A list of dict with information about the annotated methods.
    TODO(bulach): return a CalledByNative object.

  Raises:
    ParseError: if unable to parse.
  """
  called_by_natives = []
  for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
    called_by_natives += [CalledByNative(
        system_class=False,
        unchecked='Unchecked' in match.group('Unchecked'),
        static='static' in match.group('prefix'),
        java_class_name=match.group('annotation') or '',
        return_type=match.group('return_type'),
        name=match.group('name'),
        params=JniParams.Parse(match.group('params')))]
  # Check for any @CalledByNative occurrences that weren't matched.
  unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
  for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
    if '@CalledByNative' in line1:
      raise ParseError('could not parse @CalledByNative method signature',
                       line1, line2)
  return MangleCalledByNatives(called_by_natives)


class JNIFromJavaP(object):
  """Uses 'javap' to parse a .class file and generate the JNI header file."""

  def __init__(self, contents, options):
    self.contents = contents
    self.namespace = options.namespace
    for line in contents:
      class_name = re.match(
          '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
          line)
      if class_name:
        self.fully_qualified_class = class_name.group('class_name')
        break
    self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
    # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
    # away the <...> and use the raw class name that Java 6 would've given us.
    self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
    JniParams.SetFullyQualifiedClass(self.fully_qualified_class)
    self.java_class_name = self.fully_qualified_class.split('/')[-1]
    if not self.namespace:
      self.namespace = 'JNI_' + self.java_class_name
    re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
                           '\((?P<params>.*?)\)')
    self.called_by_natives = []
    for lineno, content in enumerate(contents[2:], 2):
      match = re.match(re_method, content)
      if not match:
        continue
      self.called_by_natives += [CalledByNative(
          system_class=True,
          unchecked=False,
          static='static' in match.group('prefix'),
          java_class_name='',
          return_type=match.group('return_type').replace('.', '/'),
          name=match.group('name'),
          params=JniParams.Parse(match.group('params').replace('.', '/')),
          signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
    re_constructor = re.compile('(.*?)public ' +
                                self.fully_qualified_class.replace('/', '.') +
                                '\((?P<params>.*?)\)')
    for lineno, content in enumerate(contents[2:], 2):
      match = re.match(re_constructor, content)
      if not match:
        continue
      self.called_by_natives += [CalledByNative(
          system_class=True,
          unchecked=False,
          static=False,
          java_class_name='',
          return_type=self.fully_qualified_class,
          name='Constructor',
          params=JniParams.Parse(match.group('params').replace('.', '/')),
          signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
          is_constructor=True)]
    self.called_by_natives = MangleCalledByNatives(self.called_by_natives)

    self.constant_fields = []
    re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
    re_constant_field_value = re.compile(
        '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)')
    for lineno, content in enumerate(contents[2:], 2):
      match = re.match(re_constant_field, content)
      if not match:
        continue
      value = re.match(re_constant_field_value, contents[lineno + 2])
      if not value:
        value = re.match(re_constant_field_value, contents[lineno + 3])
      if value:
        self.constant_fields.append(
            ConstantField(name=match.group('name'),
                          value=value.group('value')))

    self.inl_header_file_generator = InlHeaderFileGenerator(
        self.namespace, self.fully_qualified_class, [],
        self.called_by_natives, self.constant_fields, options)

  def GetContent(self):
    return self.inl_header_file_generator.GetContent()

  @staticmethod
  def CreateFromClass(class_file, options):
    class_name = os.path.splitext(os.path.basename(class_file))[0]
    p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
                               '-s', class_name],
                         cwd=os.path.dirname(class_file),
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    stdout, _ = p.communicate()
    jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
    return jni_from_javap


class JNIFromJavaSource(object):
  """Uses the given java source file to generate the JNI header file."""

  # Match single line comments, multiline comments, character literals, and
  # double-quoted strings.
  _comment_remover_regex = re.compile(
      r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
      re.DOTALL | re.MULTILINE)

  def __init__(self, contents, fully_qualified_class, options):
    contents = self._RemoveComments(contents)
    JniParams.SetFullyQualifiedClass(fully_qualified_class)
    JniParams.ExtractImportsAndInnerClasses(contents)
    jni_namespace = ExtractJNINamespace(contents) or options.namespace
    natives = ExtractNatives(contents, options.ptr_type)
    called_by_natives = ExtractCalledByNatives(contents)
    if len(natives) == 0 and len(called_by_natives) == 0:
      raise SyntaxError('Unable to find any JNI methods for %s.' %
                        fully_qualified_class)
    inl_header_file_generator = InlHeaderFileGenerator(
        jni_namespace, fully_qualified_class, natives, called_by_natives,
        [], options)
    self.content = inl_header_file_generator.GetContent()

  @classmethod
  def _RemoveComments(cls, contents):
    # We need to support both inline and block comments, and we need to handle
    # strings that contain '//' or '/*'.
    # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
    # parser. Maybe we could ditch JNIFromJavaSource and just always use
    # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
    # http://code.google.com/p/chromium/issues/detail?id=138941
    def replacer(match):
      # Replace matches that are comments with nothing; return literals/strings
      # unchanged.
      s = match.group(0)
      if s.startswith('/'):
        return ''
      else:
        return s
    return cls._comment_remover_regex.sub(replacer, contents)

  def GetContent(self):
    return self.content

  @staticmethod
  def CreateFromFile(java_file_name, options):
    contents = file(java_file_name).read()
    fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
                                                               contents)
    return JNIFromJavaSource(contents, fully_qualified_class, options)


class InlHeaderFileGenerator(object):
  """Generates an inline header file for JNI integration."""

  def __init__(self, namespace, fully_qualified_class, natives,
               called_by_natives, constant_fields, options):
    self.namespace = namespace
    self.fully_qualified_class = fully_qualified_class
    self.class_name = self.fully_qualified_class.split('/')[-1]
    self.natives = natives
    self.called_by_natives = called_by_natives
    self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
    self.constant_fields = constant_fields
    self.options = options
    self.init_native = self.ExtractInitNative(options)

  def ExtractInitNative(self, options):
    for native in self.natives:
      if options.jni_init_native_name == 'native' + native.name:
        self.natives.remove(native)
        return native
    return None

  def GetContent(self):
    """Returns the content of the JNI binding file."""
    template = Template("""\
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


// This file is autogenerated by
//     ${SCRIPT_NAME}
// For
//     ${FULLY_QUALIFIED_CLASS}

#ifndef ${HEADER_GUARD}
#define ${HEADER_GUARD}

#include <jni.h>

${INCLUDES}

#include "base/android/jni_int_wrapper.h"

// Step 1: forward declarations.
namespace {
$CLASS_PATH_DEFINITIONS
$METHOD_ID_DEFINITIONS
}  // namespace

$OPEN_NAMESPACE
$FORWARD_DECLARATIONS

$CONSTANT_FIELDS

// Step 2: method stubs.
$METHOD_STUBS

// Step 3: RegisterNatives.
$JNI_NATIVE_METHODS
$REGISTER_NATIVES
$CLOSE_NAMESPACE
$JNI_REGISTER_NATIVES
#endif  // ${HEADER_GUARD}
""")
    values = {
        'SCRIPT_NAME': self.options.script_name,
        'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
        'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
        'METHOD_ID_DEFINITIONS': self.GetMethodIDDefinitionsString(),
        'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(),
        'CONSTANT_FIELDS': self.GetConstantFieldsString(),
        'METHOD_STUBS': self.GetMethodStubsString(),
        'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
        'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(),
        'REGISTER_NATIVES': self.GetRegisterNativesString(),
        'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
        'HEADER_GUARD': self.header_guard,
        'INCLUDES': self.GetIncludesString(),
        'JNI_REGISTER_NATIVES': self.GetJNIRegisterNativesString()
    }
    return WrapOutput(template.substitute(values))

  def GetClassPathDefinitionsString(self):
    ret = []
    ret += [self.GetClassPathDefinitions()]
    return '\n'.join(ret)

  def GetMethodIDDefinitionsString(self):
    """Returns the definition of method ids for the called by native methods."""
    if not self.options.eager_called_by_natives:
      return ''
    template = Template("""\
jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = NULL;""")
    ret = []
    for called_by_native in self.called_by_natives:
      values = {
          'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
          'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
      }
      ret += [template.substitute(values)]
    return '\n'.join(ret)

  def GetForwardDeclarationsString(self):
    ret = []
    for native in self.natives:
      if native.type != 'method':
        ret += [self.GetForwardDeclaration(native)]
    return '\n'.join(ret)

  def GetConstantFieldsString(self):
    if not self.constant_fields:
      return ''
    ret = ['enum Java_%s_constant_fields {' % self.class_name]
    for c in self.constant_fields:
      ret += ['  %s = %s,' % (c.name, c.value)]
    ret += ['};']
    return '\n'.join(ret)

  def GetMethodStubsString(self):
    """Returns the code corresponding to method stubs."""
    ret = []
    for native in self.natives:
      if native.type == 'method':
        ret += [self.GetNativeMethodStubString(native)]
    if self.options.eager_called_by_natives:
      ret += self.GetEagerCalledByNativeMethodStubs()
    else:
      ret += self.GetLazyCalledByNativeMethodStubs()
    return '\n'.join(ret)

  def GetLazyCalledByNativeMethodStubs(self):
    return [self.GetLazyCalledByNativeMethodStub(called_by_native)
            for called_by_native in self.called_by_natives]

  def GetEagerCalledByNativeMethodStubs(self):
    ret = []
    if self.called_by_natives:
      ret += ['namespace {']
      for called_by_native in self.called_by_natives:
        ret += [self.GetEagerCalledByNativeMethodStub(called_by_native)]
      ret += ['}  // namespace']
    return ret

  def GetIncludesString(self):
    if not self.options.includes:
      return ''
    includes = self.options.includes.split(',')
    return '\n'.join('#include "%s"' % x for x in includes)

  def GetKMethodsString(self, clazz):
    ret = []
    for native in self.natives:
      if (native.java_class_name == clazz or
          (not native.java_class_name and clazz == self.class_name)):
        ret += [self.GetKMethodArrayEntry(native)]
    return '\n'.join(ret)

  def SubstituteNativeMethods(self, template):
    """Substitutes JAVA_CLASS and KMETHODS in the provided template."""
    ret = []
    all_classes = self.GetUniqueClasses(self.natives)
    all_classes[self.class_name] = self.fully_qualified_class
    for clazz in all_classes:
      kmethods = self.GetKMethodsString(clazz)
      if kmethods:
        values = {'JAVA_CLASS': clazz,
                  'KMETHODS': kmethods}
        ret += [template.substitute(values)]
    if not ret: return ''
    return '\n' + '\n'.join(ret)

  def GetJNINativeMethodsString(self):
    """Returns the implementation of the array of native methods."""
    template = Template("""\
static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
${KMETHODS}
};
""")
    return self.SubstituteNativeMethods(template)

  def GetRegisterCalledByNativesImplString(self):
    """Returns the code for registering the called by native methods."""
    if not self.options.eager_called_by_natives:
      return ''
    template = Template("""\
  g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = ${GET_METHOD_ID_IMPL}
  if (g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} == NULL) {
    return false;
  }
    """)
    ret = []
    for called_by_native in self.called_by_natives:
      values = {
          'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
          'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
          'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native),
      }
      ret += [template.substitute(values)]
    return '\n'.join(ret)

  def GetRegisterNativesString(self):
    """Returns the code for RegisterNatives."""
    template = Template("""\
${REGISTER_NATIVES_SIGNATURE} {
${CLASSES}
${NATIVES}
${CALLED_BY_NATIVES}
  return true;
}
""")
    signature = 'static bool RegisterNativesImpl(JNIEnv* env'
    if self.init_native:
      signature += ', jclass clazz)'
    else:
      signature += ')'

    natives = self.GetRegisterNativesImplString()
    called_by_natives = self.GetRegisterCalledByNativesImplString()
    values = {'REGISTER_NATIVES_SIGNATURE': signature,
              'CLASSES': self.GetFindClasses(),
              'NATIVES': natives,
              'CALLED_BY_NATIVES': called_by_natives,
             }
    return template.substitute(values)

  def GetRegisterNativesImplString(self):
    """Returns the shared implementation for RegisterNatives."""
    template = Template("""\
  const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});

  if (env->RegisterNatives(g_${JAVA_CLASS}_clazz,
                           kMethods${JAVA_CLASS},
                           kMethods${JAVA_CLASS}Size) < 0) {
    jni_generator::HandleRegistrationError(
        env, g_${JAVA_CLASS}_clazz, __FILE__);
    return false;
  }
""")
    return self.SubstituteNativeMethods(template)

  def GetJNIRegisterNativesString(self):
    """Returns the implementation for the JNI registration of native methods."""
    if not self.init_native:
      return ''

    template = Template("""\
extern "C" JNIEXPORT bool JNICALL
Java_${FULLY_QUALIFIED_CLASS}_${INIT_NATIVE_NAME}(JNIEnv* env, jclass clazz) {
  return ${NAMESPACE}RegisterNativesImpl(env, clazz);
}
""")
    fully_qualified_class = self.fully_qualified_class.replace('/', '_')
    namespace = ''
    if self.namespace:
      namespace = self.namespace + '::'
    values = {'FULLY_QUALIFIED_CLASS': fully_qualified_class,
              'INIT_NATIVE_NAME': 'native' + self.init_native.name,
              'NAMESPACE': namespace,
              'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString()
             }
    return template.substitute(values)

  def GetOpenNamespaceString(self):
    if self.namespace:
      all_namespaces = ['namespace %s {' % ns
                        for ns in self.namespace.split('::')]
      return '\n'.join(all_namespaces)
    return ''

  def GetCloseNamespaceString(self):
    if self.namespace:
      all_namespaces = ['}  // namespace %s' % ns
                        for ns in self.namespace.split('::')]
      all_namespaces.reverse()
      return '\n'.join(all_namespaces) + '\n'
    return ''

  def GetJNIFirstParam(self, native):
    ret = []
    if native.type == 'method':
      ret = ['jobject jcaller']
    elif native.type == 'function':
      if native.static:
        ret = ['jclass jcaller']
      else:
        ret = ['jobject jcaller']
    return ret

  def GetParamsInDeclaration(self, native):
    """Returns the params for the stub declaration.

    Args:
      native: the native dictionary describing the method.

    Returns:
      A string containing the params.
    """
    return ',\n    '.join(self.GetJNIFirstParam(native) +
                          [JavaDataTypeToC(param.datatype) + ' ' +
                           param.name
                           for param in native.params])

  def GetCalledByNativeParamsInDeclaration(self, called_by_native):
    return ',\n    '.join([
        JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
        param.name
        for param in called_by_native.params])

  def GetForwardDeclaration(self, native):
    template = Template("""
static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
""")
    values = {'RETURN': JavaDataTypeToC(native.return_type),
              'NAME': native.name,
              'PARAMS': self.GetParamsInDeclaration(native)}
    return template.substitute(values)

  def GetNativeMethodStubString(self, native):
    """Returns stubs for native methods."""
    template = Template("""\
static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {
  ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
  CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
  return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
}
""")
    params = []
    if not self.options.pure_native_methods:
      params = ['env', 'jcaller']
    params_in_call = ', '.join(params + [p.name for p in native.params[1:]])

    return_type = JavaDataTypeToC(native.return_type)
    optional_error_return = JavaReturnValueToC(native.return_type)
    if optional_error_return:
      optional_error_return = ', ' + optional_error_return
    post_call = ''
    if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
      post_call = '.Release()'
    values = {
        'RETURN': return_type,
        'OPTIONAL_ERROR_RETURN': optional_error_return,
        'NAME': native.name,
        'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native),
        'PARAM0_NAME': native.params[0].name,
        'P0_TYPE': native.p0_type,
        'PARAMS_IN_CALL': params_in_call,
        'POST_CALL': post_call
    }
    return template.substitute(values)

  def GetArgument(self, param):
    return ('as_jint(' + param.name + ')'
            if param.datatype == 'int' else param.name)

  def GetArgumentsInCall(self, params):
    """Return a string of arguments to call from native into Java"""
    return [self.GetArgument(p) for p in params]

  def GetCalledByNativeValues(self, called_by_native):
    """Fills in necessary values for the CalledByNative methods."""
    if called_by_native.static or called_by_native.is_constructor:
      first_param_in_declaration = ''
      first_param_in_call = ('g_%s_clazz' %
                             (called_by_native.java_class_name or
                              self.class_name))
    else:
      first_param_in_declaration = ', jobject obj'
      first_param_in_call = 'obj'
    params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
        called_by_native)
    if params_in_declaration:
      params_in_declaration = ', ' + params_in_declaration
    params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
    if params_in_call:
      params_in_call = ', ' + params_in_call
    pre_call = ''
    post_call = ''
    if called_by_native.static_cast:
      pre_call = 'static_cast<%s>(' % called_by_native.static_cast
      post_call = ')'
    check_exception = ''
    if not called_by_native.unchecked:
      check_exception = 'jni_generator::CheckException(env);'
    return_type = JavaDataTypeToC(called_by_native.return_type)
    optional_error_return = JavaReturnValueToC(called_by_native.return_type)
    if optional_error_return:
      optional_error_return = ', ' + optional_error_return
    return_declaration = ''
    return_clause = ''
    if return_type != 'void':
      pre_call = ' ' + pre_call
      return_declaration = return_type + ' ret ='
      if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
        return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
        return_clause = 'return ' + return_type + '(env, ret);'
      else:
        return_clause = 'return ret;'
    return {
        'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
        'RETURN_TYPE': return_type,
        'OPTIONAL_ERROR_RETURN': optional_error_return,
        'RETURN_DECLARATION': return_declaration,
        'RETURN_CLAUSE': return_clause,
        'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
        'PARAMS_IN_DECLARATION': params_in_declaration,
        'PRE_CALL': pre_call,
        'POST_CALL': post_call,
        'ENV_CALL': called_by_native.env_call,
        'FIRST_PARAM_IN_CALL': first_param_in_call,
        'PARAMS_IN_CALL': params_in_call,
        'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
        'CHECK_EXCEPTION': check_exception,
        'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native)
    }

  def GetEagerCalledByNativeMethodStub(self, called_by_native):
    """Returns the implementation of the called by native method."""
    template = Template("""
static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\
JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION}) {
  ${RETURN_DECLARATION}${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
      g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL};
  ${RETURN_CLAUSE}
}""")
    values = self.GetCalledByNativeValues(called_by_native)
    return template.substitute(values)

  def GetLazyCalledByNativeMethodStub(self, called_by_native):
    """Returns a string."""
    function_signature_template = Template("""\
static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
    function_header_template = Template("""\
${FUNCTION_SIGNATURE} {""")
    function_header_with_unused_template = Template("""\
${FUNCTION_SIGNATURE} __attribute__ ((unused));
${FUNCTION_SIGNATURE} {""")
    template = Template("""
static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
${FUNCTION_HEADER}
  /* Must call RegisterNativesImpl()  */
  CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
      g_${JAVA_CLASS}_clazz${OPTIONAL_ERROR_RETURN});
  jmethodID method_id =
    ${GET_METHOD_ID_IMPL}
  ${RETURN_DECLARATION}
     ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
          method_id${PARAMS_IN_CALL})${POST_CALL};
  ${CHECK_EXCEPTION}
  ${RETURN_CLAUSE}
}""")
    values = self.GetCalledByNativeValues(called_by_native)
    values['FUNCTION_SIGNATURE'] = (
        function_signature_template.substitute(values))
    if called_by_native.system_class:
      values['FUNCTION_HEADER'] = (
          function_header_with_unused_template.substitute(values))
    else:
      values['FUNCTION_HEADER'] = function_header_template.substitute(values)
    return template.substitute(values)

  def GetKMethodArrayEntry(self, native):
    template = Template("""\
    { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""")
    values = {'NAME': native.name,
              'JNI_SIGNATURE': JniParams.Signature(native.params,
                                                   native.return_type,
                                                   True)}
    return template.substitute(values)

  def GetUniqueClasses(self, origin):
    ret = {self.class_name: self.fully_qualified_class}
    for entry in origin:
      class_name = self.class_name
      jni_class_path = self.fully_qualified_class
      if entry.java_class_name:
        class_name = entry.java_class_name
        jni_class_path = self.fully_qualified_class + '$' + class_name
      ret[class_name] = jni_class_path
    return ret

  def GetClassPathDefinitions(self):
    """Returns the ClassPath constants."""
    ret = []
    template = Template("""\
const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
    native_classes = self.GetUniqueClasses(self.natives)
    called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
    all_classes = native_classes
    all_classes.update(called_by_native_classes)
    for clazz in all_classes:
      values = {
          'JAVA_CLASS': clazz,
          'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]),
      }
      ret += [template.substitute(values)]
    ret += ''
    for clazz in called_by_native_classes:
      template = Template("""\
// Leaking this jclass as we cannot use LazyInstance from some threads.
jclass g_${JAVA_CLASS}_clazz = NULL;""")
      values = {
          'JAVA_CLASS': clazz,
      }
      ret += [template.substitute(values)]
    return '\n'.join(ret)

  def GetFindClasses(self):
    """Returns the imlementation of FindClass for all known classes."""
    if self.init_native:
      template = Template("""\
  g_${JAVA_CLASS}_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));""")
    else:
      template = Template("""\
  g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
      base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
    ret = []
    for clazz in self.GetUniqueClasses(self.called_by_natives):
      values = {'JAVA_CLASS': clazz}
      ret += [template.substitute(values)]
    return '\n'.join(ret)

  def GetMethodIDImpl(self, called_by_native):
    """Returns the implementation of GetMethodID."""
    if self.options.eager_called_by_natives:
      template = Template("""\
env->Get${STATIC_METHOD_PART}MethodID(
      g_${JAVA_CLASS}_clazz,
      "${JNI_NAME}", ${JNI_SIGNATURE});""")
    else:
      template = Template("""\
  base::android::MethodID::LazyGet<
      base::android::MethodID::TYPE_${STATIC}>(
      env, g_${JAVA_CLASS}_clazz,
      "${JNI_NAME}",
      ${JNI_SIGNATURE},
      &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
""")
    jni_name = called_by_native.name
    jni_return_type = called_by_native.return_type
    if called_by_native.is_constructor:
      jni_name = '<init>'
      jni_return_type = 'void'
    if called_by_native.signature:
      signature = called_by_native.signature
    else:
      signature = JniParams.Signature(called_by_native.params,
                                      jni_return_type,
                                      True)
    values = {
        'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
        'JNI_NAME': jni_name,
        'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
        'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
        'STATIC_METHOD_PART': 'Static' if called_by_native.static else '',
        'JNI_SIGNATURE': signature,
    }
    return template.substitute(values)


def WrapOutput(output):
  ret = []
  for line in output.splitlines():
    # Do not wrap lines under 80 characters or preprocessor directives.
    if len(line) < 80 or line.lstrip()[:1] == '#':
      stripped = line.rstrip()
      if len(ret) == 0 or len(ret[-1]) or len(stripped):
        ret.append(stripped)
    else:
      first_line_indent = ' ' * (len(line) - len(line.lstrip()))
      subsequent_indent =  first_line_indent + ' ' * 4
      if line.startswith('//'):
        subsequent_indent = '//' + subsequent_indent
      wrapper = textwrap.TextWrapper(width=80,
                                     subsequent_indent=subsequent_indent,
                                     break_long_words=False)
      ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
  ret += ['']
  return '\n'.join(ret)


def ExtractJarInputFile(jar_file, input_file, out_dir):
  """Extracts input file from jar and returns the filename.

  The input file is extracted to the same directory that the generated jni
  headers will be placed in.  This is passed as an argument to script.

  Args:
    jar_file: the jar file containing the input files to extract.
    input_files: the list of files to extract from the jar file.
    out_dir: the name of the directories to extract to.

  Returns:
    the name of extracted input file.
  """
  jar_file = zipfile.ZipFile(jar_file)

  out_dir = os.path.join(out_dir, os.path.dirname(input_file))
  try:
    os.makedirs(out_dir)
  except OSError as e:
    if e.errno != errno.EEXIST:
      raise
  extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
  with open(extracted_file_name, 'w') as outfile:
    outfile.write(jar_file.read(input_file))

  return extracted_file_name


def GenerateJNIHeader(input_file, output_file, options):
  try:
    if os.path.splitext(input_file)[1] == '.class':
      jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
      content = jni_from_javap.GetContent()
    else:
      jni_from_java_source = JNIFromJavaSource.CreateFromFile(
          input_file, options)
      content = jni_from_java_source.GetContent()
  except ParseError, e:
    print e
    sys.exit(1)
  if output_file:
    if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
      os.makedirs(os.path.dirname(os.path.abspath(output_file)))
    if options.optimize_generation and os.path.exists(output_file):
      with file(output_file, 'r') as f:
        existing_content = f.read()
        if existing_content == content:
          return
    with file(output_file, 'w') as f:
      f.write(content)
  else:
    print output


def GetScriptName():
  script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
  base_index = 0
  for idx, value in enumerate(script_components):
    if value == 'base' or value == 'third_party':
      base_index = idx
      break
  return os.sep.join(script_components[base_index:])


def main(argv):
  usage = """usage: %prog [OPTIONS]
This script will parse the given java source code extracting the native
declarations and print the header file to stdout (or a file).
See SampleForTests.java for more details.
  """
  option_parser = optparse.OptionParser(usage=usage)
  option_parser.add_option('-j', '--jar_file', dest='jar_file',
                           help='Extract the list of input files from'
                           ' a specified jar file.'
                           ' Uses javap to extract the methods from a'
                           ' pre-compiled class. --input should point'
                           ' to pre-compiled Java .class files.')
  option_parser.add_option('-n', dest='namespace',
                           help='Uses as a namespace in the generated header '
                           'instead of the javap class name, or when there is '
                           'no JNINamespace annotation in the java source.')
  option_parser.add_option('--input_file',
                           help='Single input file name. The output file name '
                           'will be derived from it. Must be used with '
                           '--output_dir.')
  option_parser.add_option('--output_dir',
                           help='The output directory. Must be used with '
                           '--input')
  option_parser.add_option('--optimize_generation', type="int",
                           default=0, help='Whether we should optimize JNI '
                           'generation by not regenerating files if they have '
                           'not changed.')
  option_parser.add_option('--jarjar',
                           help='Path to optional jarjar rules file.')
  option_parser.add_option('--script_name', default=GetScriptName(),
                           help='The name of this script in the generated '
                           'header.')
  option_parser.add_option('--includes',
                           help='The comma-separated list of header files to '
                           'include in the generated header.')
  option_parser.add_option('--pure_native_methods',
                           action='store_true', dest='pure_native_methods',
                           help='When true, the native methods will be called '
                           'without any JNI-specific arguments.')
  option_parser.add_option('--ptr_type', default='int',
                           type='choice', choices=['int', 'long'],
                           help='The type used to represent native pointers in '
                           'Java code. For 32-bit, use int; '
                           'for 64-bit, use long.')
  option_parser.add_option('--jni_init_native_name', default='',
                           help='The name of the JNI registration method that '
                           'is used to initialize all native methods. If a '
                           'method with this name is not present in the Java '
                           'source file, setting this option is a no-op. When '
                           'a method with this name is found however, the '
                           'naming convention Java_<packageName>_<className> '
                           'will limit the initialization to only the '
                           'top-level class.')
  option_parser.add_option('--eager_called_by_natives',
                           action='store_true', dest='eager_called_by_natives',
                           help='When true, the called-by-native methods will '
                           'be initialized in a non-atomic way.')
  option_parser.add_option('--cpp', default='cpp',
                           help='The path to cpp command.')
  option_parser.add_option('--javap', default='javap',
                           help='The path to javap command.')
  options, args = option_parser.parse_args(argv)
  if options.jar_file:
    input_file = ExtractJarInputFile(options.jar_file, options.input_file,
                                     options.output_dir)
  elif options.input_file:
    input_file = options.input_file
  else:
    option_parser.print_help()
    print '\nError: Must specify --jar_file or --input_file.'
    return 1
  output_file = None
  if options.output_dir:
    root_name = os.path.splitext(os.path.basename(input_file))[0]
    output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
  if options.jarjar:
    with open(options.jarjar) as f:
      JniParams.SetJarJarMappings(f.read())
  GenerateJNIHeader(input_file, output_file, options)


if __name__ == '__main__':
  sys.exit(main(sys.argv))
