diff options
Diffstat (limited to 'base/android/jni_generator/jni_generator.py')
-rwxr-xr-x | base/android/jni_generator/jni_generator.py | 1418 |
1 files changed, 0 insertions, 1418 deletions
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py deleted file mode 100755 index 99d8b42..0000000 --- a/base/android/jni_generator/jni_generator.py +++ /dev/null @@ -1,1418 +0,0 @@ -#!/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 - -CHROMIUM_SRC = os.path.join( - os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) -BUILD_ANDROID_GYP = os.path.join( - CHROMIUM_SRC, 'build', 'android', 'gyp') - -sys.path.append(BUILD_ANDROID_GYP) - -from util import build_utils - - -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', - 'Throwable': 'jthrowable', - 'java/lang/String': 'jstring', - 'java/lang/Class': 'jclass', - 'java/lang/Throwable': 'jthrowable', - } - - 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 WrapCTypeForDeclaration(c_type): - """Wrap the C datatype in a JavaRef if required.""" - if re.match(RE_SCOPED_JNI_TYPES, c_type): - return 'const base::android::JavaParamRef<' + c_type + '>&' - else: - return c_type - - -def JavaDataTypeToCForDeclaration(java_type): - """Returns a JavaRef-wrapped C datatype for the given java type.""" - return WrapCTypeForDeclaration(JavaDataTypeToC(java_type)) - - -def JavaDataTypeToCForCalledByNativeParam(java_type): - """Returns a C datatype to be when calling from native.""" - if java_type == 'int': - return 'JniIntWrapper' - else: - c_type = JavaDataTypeToC(java_type) - if re.match(RE_SCOPED_JNI_TYPES, c_type): - return 'const base::android::JavaRefOrBare<' + c_type + '>&' - else: - return c_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 = [] - _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: ' - index = signature_line.find(prefix) - if index == -1: - prefix = 'descriptor: ' - index = signature_line.index(prefix) - return '"%s"' % signature_line[index + 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', - 'Ljava/lang/CharSequence', - 'Ljava/lang/Runnable', - 'Ljava/lang/Throwable', - ] - - 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' + 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 + 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 + 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 + 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._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(' ') - - # Remove @Annotations from parameters. - while items[0].startswith('@'): - del items[0] - - 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 - - -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 IsMainDexJavaClass(contents): - """Returns "true" if the class is annotated with "@MainDex", "false" if not. - - JNI registration doesn't always need to be completed for non-browser processes - since most Java code is only used by the browser process. Classes that are - needed by non-browser processes must explicitly be annotated with @MainDex - to force JNI registration. - """ - re_maindex = re.compile(r'@MainDex[\s\S]*class\s+\w+\s*{') - found = re.search(re_maindex, contents) - return 'true' if found else 'false' - - -def GetStaticCastForReturnType(return_type): - type_map = { 'String' : 'jstring', - 'java/lang/String' : 'jstring', - 'Throwable': 'jthrowable', - 'java/lang/Throwable': 'jthrowable', - '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 types that should be wrapped in a JavaRef. -RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*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*@\w+)?' # Ignore annotations in return types. - '\s*(?P<return_type>\S+?)' - '\s+(?P<name>\w+)' - '\s*\((?P<params>[^\)]*)\)') - - -# Removes empty lines that are indented (i.e. start with 2x spaces). -def RemoveIndentedEmptyLines(string): - return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE) - - -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) - maindex = IsMainDexJavaClass(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, maindex) - 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, maindex='false'): - 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.maindex = maindex - self.options = options - - - def GetContent(self): - """Returns the content of the JNI binding file.""" - template = Template("""\ -// Copyright 2014 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 - -} // namespace - -$OPEN_NAMESPACE - -$CONSTANT_FIELDS - -// Step 2: method stubs. -$METHOD_STUBS - -// Step 3: RegisterNatives. -$JNI_NATIVE_METHODS -$REGISTER_NATIVES -$CLOSE_NAMESPACE - -#endif // ${HEADER_GUARD} -""") - values = { - 'SCRIPT_NAME': self.options.script_name, - 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, - 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), - '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(), - } - assert ((values['JNI_NATIVE_METHODS'] == '') == - (values['REGISTER_NATIVES'] == '')) - return WrapOutput(template.substitute(values)) - - def GetClassPathDefinitionsString(self): - ret = [] - ret += [self.GetClassPathDefinitions()] - 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: - ret += [self.GetNativeStub(native)] - 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 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.""" - if not self.options.native_exports_optional: - return '' - template = Template("""\ -static const JNINativeMethod kMethods${JAVA_CLASS}[] = { -${KMETHODS} -}; -""") - return self.SubstituteNativeMethods(template) - - def GetRegisterNativesString(self): - """Returns the code for RegisterNatives.""" - natives = self.GetRegisterNativesImplString() - if not natives: - return '' - - template = Template("""\ -${REGISTER_NATIVES_SIGNATURE} { -${EARLY_EXIT} -${NATIVES} - return true; -} -""") - signature = 'static bool RegisterNativesImpl(JNIEnv* env)' - early_exit = '' - if self.options.native_exports_optional: - early_exit = """\ - if (jni_generator::ShouldSkipJniRegistration(%s)) - return true; -""" % self.maindex - - values = {'REGISTER_NATIVES_SIGNATURE': signature, - 'EARLY_EXIT': early_exit, - 'NATIVES': natives, - } - - return template.substitute(values) - - def GetRegisterNativesImplString(self): - """Returns the shared implementation for RegisterNatives.""" - if not self.options.native_exports_optional: - return '' - - template = Template("""\ - const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS}); - - if (env->RegisterNatives(${JAVA_CLASS}_clazz(env), - kMethods${JAVA_CLASS}, - kMethods${JAVA_CLASS}Size) < 0) { - jni_generator::HandleRegistrationError( - env, ${JAVA_CLASS}_clazz(env), __FILE__); - return false; - } -""") - return self.SubstituteNativeMethods(template) - - 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 GetJNIFirstParamType(self, native): - if native.type == 'method': - return 'jobject' - elif native.type == 'function': - if native.static: - return 'jclass' - else: - return 'jobject' - - def GetJNIFirstParam(self, native, for_declaration): - c_type = self.GetJNIFirstParamType(native) - if for_declaration: - c_type = WrapCTypeForDeclaration(c_type) - return [c_type + ' jcaller'] - - def GetParamsInDeclaration(self, native): - """Returns the params for the forward declaration. - - Args: - native: the native dictionary describing the method. - - Returns: - A string containing the params. - """ - return ',\n '.join(self.GetJNIFirstParam(native, True) + - [JavaDataTypeToCForDeclaration(param.datatype) + ' ' + - param.name - for param in native.params]) - - def GetParamsInStub(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, False) + - [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 GetStubName(self, native): - """Return the name of the stub function for this native method. - - Args: - native: the native dictionary describing the method. - - Returns: - A string with the stub function name (used by the JVM). - """ - template = Template("Java_${JAVA_NAME}_native${NAME}") - - java_name = self.fully_qualified_class.replace('_', '_1').replace('/', '_') - if native.java_class_name: - java_name += '_00024' + native.java_class_name - - values = {'NAME': native.name, - 'JAVA_NAME': java_name} - return template.substitute(values) - - def GetJavaParamRefForCall(self, c_type, name): - return Template( - 'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({ - 'TYPE': c_type, - 'NAME': name, - }) - - def GetJNIFirstParamForCall(self, native): - c_type = self.GetJNIFirstParamType(native) - return [self.GetJavaParamRefForCall(c_type, 'jcaller')] - - def GetNativeStub(self, native): - is_method = native.type == 'method' - - if is_method: - params = native.params[1:] - else: - params = native.params - params_in_call = ['env'] + self.GetJNIFirstParamForCall(native) - for p in params: - c_type = JavaDataTypeToC(p.datatype) - if re.match(RE_SCOPED_JNI_TYPES, c_type): - params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name)) - else: - params_in_call.append(p.name) - params_in_call = ', '.join(params_in_call) - - return_type = return_declaration = JavaDataTypeToC(native.return_type) - post_call = '' - if re.match(RE_SCOPED_JNI_TYPES, return_type): - post_call = '.Release()' - return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type + - '>') - profiling_entered_native = '' - if self.options.enable_profiling: - profiling_entered_native = 'JNI_LINK_SAVED_FRAME_POINTER;' - values = { - 'RETURN': return_type, - 'RETURN_DECLARATION': return_declaration, - 'NAME': native.name, - 'PARAMS': self.GetParamsInDeclaration(native), - 'PARAMS_IN_STUB': self.GetParamsInStub(native), - 'PARAMS_IN_CALL': params_in_call, - 'POST_CALL': post_call, - 'STUB_NAME': self.GetStubName(native), - 'PROFILING_ENTERED_NATIVE': profiling_entered_native, - } - - if is_method: - optional_error_return = JavaReturnValueToC(native.return_type) - if optional_error_return: - optional_error_return = ', ' + optional_error_return - values.update({ - 'OPTIONAL_ERROR_RETURN': optional_error_return, - 'PARAM0_NAME': native.params[0].name, - 'P0_TYPE': native.p0_type, - }) - template = Template("""\ -JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) { - ${PROFILING_ENTERED_NATIVE} - ${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}; -} -""") - else: - template = Template(""" -static ${RETURN_DECLARATION} ${NAME}(JNIEnv* env, ${PARAMS}); - -JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) { - ${PROFILING_ENTERED_NATIVE} - return ${NAME}(${PARAMS_IN_CALL})${POST_CALL}; -} -""") - - return RemoveIndentedEmptyLines(template.substitute(values)) - - def GetArgument(self, param): - if param.datatype == 'int': - return 'as_jint(' + param.name + ')' - elif re.match(RE_SCOPED_JNI_TYPES, JavaDataTypeToC(param.datatype)): - return param.name + '.obj()' - else: - return 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.""" - java_class = called_by_native.java_class_name or self.class_name - if called_by_native.static or called_by_native.is_constructor: - first_param_in_declaration = '' - first_param_in_call = ('%s_clazz(env)' % java_class) - else: - first_param_in_declaration = ( - ', const base::android::JavaRefOrBare<jobject>& obj') - first_param_in_call = 'obj.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_TYPES, return_type): - return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>' - return_clause = 'return ' + return_type + '(env, ret);' - else: - return_clause = 'return ret;' - profiling_leaving_native = '' - if self.options.enable_profiling: - profiling_leaving_native = 'JNI_SAVE_FRAME_POINTER;' - return { - 'JAVA_CLASS': java_class, - '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), - 'PROFILING_LEAVING_NATIVE': profiling_leaving_native, - } - - - 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} - CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL}, - ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN}); - jmethodID method_id = - ${GET_METHOD_ID_IMPL} - ${PROFILING_LEAVING_NATIVE} - ${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 RemoveIndentedEmptyLines(template.substitute(values)) - - def GetKMethodArrayEntry(self, native): - template = Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' + - 'reinterpret_cast<void*>(${STUB_NAME}) },') - values = {'NAME': native.name, - 'JNI_SIGNATURE': JniParams.Signature(native.params, - native.return_type, - True), - 'STUB_NAME': self.GetStubName(native)} - 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}";""") - all_classes = self.GetUniqueClasses(self.called_by_natives) - if self.options.native_exports_optional: - all_classes.update(self.GetUniqueClasses(self.natives)) - - for clazz in all_classes: - values = { - 'JAVA_CLASS': clazz, - 'JNI_CLASS_PATH': all_classes[clazz], - } - ret += [template.substitute(values)] - ret += '' - - template = Template("""\ -// Leaking this jclass as we cannot use LazyInstance from some threads. -base::subtle::AtomicWord g_${JAVA_CLASS}_clazz __attribute__((unused)) = 0; -#define ${JAVA_CLASS}_clazz(env) \ -base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \ -&g_${JAVA_CLASS}_clazz)""") - - for clazz in all_classes: - values = { - 'JAVA_CLASS': clazz, - } - ret += [template.substitute(values)] - - return '\n'.join(ret) - - def GetMethodIDImpl(self, called_by_native): - """Returns the implementation of GetMethodID.""" - template = Template("""\ - base::android::MethodID::LazyGet< - base::android::MethodID::TYPE_${STATIC}>( - env, ${JAVA_CLASS}_clazz(env), - "${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', - '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 content - - -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) - build_utils.AddDepfileOption(option_parser) - - 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('--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('--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('--cpp', default='cpp', - help='The path to cpp command.') - option_parser.add_option('--javap', default='javap', - help='The path to javap command.') - option_parser.add_option('--native_exports_optional', action='store_true', - help='Support both explicit and native method' - 'registration.') - option_parser.add_option('--enable_profiling', action='store_true', - help='Add additional profiling instrumentation.') - 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' - GenerateJNIHeader(input_file, output_file, options) - - if options.depfile: - build_utils.WriteDepfile(options.depfile, output_file) - - -if __name__ == '__main__': - sys.exit(main(sys.argv)) |