2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Extracts native methods from a Java file and generates the JNI bindings.
7 If you change this, please run and update the tests."""
15 from string import Template
22 class ParseError(Exception):
23 """Exception thrown when we can't parse the input file."""
25 def __init__(self, description, *context_lines):
26 Exception.__init__(self)
27 self.description = description
28 self.context_lines = context_lines
31 context = '\n'.join(self.context_lines)
32 return '***\nERROR: %s\n\n%s\n***' % (self.description, context)
36 """Describes a param for a method, either java or native."""
38 def __init__(self, **kwargs):
39 self.datatype = kwargs['datatype']
40 self.name = kwargs['name']
43 class NativeMethod(object):
44 """Describes a C/C++ method that is called by Java code"""
46 def __init__(self, **kwargs):
47 self.static = kwargs['static']
48 self.java_class_name = kwargs['java_class_name']
49 self.return_type = kwargs['return_type']
50 self.name = kwargs['name']
51 self.params = kwargs['params']
53 assert type(self.params) is list
54 assert type(self.params[0]) is Param
56 self.params[0].datatype == kwargs.get('ptr_type', 'int') and
57 self.params[0].name.startswith('native')):
59 self.p0_type = self.params[0].name[len('native'):]
60 if kwargs.get('native_class_name'):
61 self.p0_type = kwargs['native_class_name']
63 self.type = 'function'
64 self.method_id_var_name = kwargs.get('method_id_var_name', None)
67 class CalledByNative(object):
68 """Describes a java method exported to c/c++"""
70 def __init__(self, **kwargs):
71 self.system_class = kwargs['system_class']
72 self.unchecked = kwargs['unchecked']
73 self.static = kwargs['static']
74 self.java_class_name = kwargs['java_class_name']
75 self.return_type = kwargs['return_type']
76 self.name = kwargs['name']
77 self.params = kwargs['params']
78 self.method_id_var_name = kwargs.get('method_id_var_name', None)
79 self.signature = kwargs.get('signature')
80 self.is_constructor = kwargs.get('is_constructor', False)
81 self.env_call = GetEnvCall(self.is_constructor, self.static,
83 self.static_cast = GetStaticCastForReturnType(self.return_type)
86 class ConstantField(object):
87 def __init__(self, **kwargs):
88 self.name = kwargs['name']
89 self.value = kwargs['value']
92 def JavaDataTypeToC(java_type):
93 """Returns a C datatype for the given java type."""
99 'boolean': 'jboolean',
107 'java/lang/String': 'jstring',
108 'java/lang/Class': 'jclass',
111 if java_type in java_pod_type_map:
112 return java_pod_type_map[java_type]
113 elif java_type in java_type_map:
114 return java_type_map[java_type]
115 elif java_type.endswith('[]'):
116 if java_type[:-2] in java_pod_type_map:
117 return java_pod_type_map[java_type[:-2]] + 'Array'
118 return 'jobjectArray'
119 elif java_type.startswith('Class'):
120 # Checking just the start of the name, rather than a direct comparison,
121 # in order to handle generics.
127 def JavaDataTypeToCForCalledByNativeParam(java_type):
128 """Returns a C datatype to be when calling from native."""
129 if java_type == 'int':
130 return 'JniIntWrapper'
132 return JavaDataTypeToC(java_type)
135 def JavaReturnValueToC(java_type):
136 """Returns a valid C return value for the given java type."""
137 java_pod_type_map = {
148 return java_pod_type_map.get(java_type, 'NULL')
151 class JniParams(object):
153 _fully_qualified_class = ''
157 _implicit_imports = []
160 def SetFullyQualifiedClass(fully_qualified_class):
161 JniParams._fully_qualified_class = 'L' + fully_qualified_class
162 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1])
165 def ExtractImportsAndInnerClasses(contents):
166 contents = contents.replace('\n', '')
167 re_import = re.compile(r'import.*?(?P<class>\S*?);')
168 for match in re.finditer(re_import, contents):
169 JniParams._imports += ['L' + match.group('class').replace('.', '/')]
171 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W')
172 for match in re.finditer(re_inner, contents):
173 inner = match.group('name')
174 if not JniParams._fully_qualified_class.endswith(inner):
175 JniParams._inner_classes += [JniParams._fully_qualified_class + '$' +
179 def ParseJavaPSignature(signature_line):
180 prefix = 'Signature: '
181 return '"%s"' % signature_line[signature_line.index(prefix) + len(prefix):]
184 def JavaToJni(param):
185 """Converts a java param into a JNI signature type."""
197 object_param_list = [
198 'Ljava/lang/Boolean',
199 'Ljava/lang/Integer',
208 while param[-2:] == '[]':
213 param = param[:param.index('<')]
214 if param in pod_param_map:
215 return prefix + pod_param_map[param]
217 # Coming from javap, use the fully qualified param directly.
218 return prefix + 'L' + JniParams.RemapClassName(param) + ';'
220 for qualified_name in (object_param_list +
221 [JniParams._fully_qualified_class] +
222 JniParams._inner_classes):
223 if (qualified_name.endswith('/' + param) or
224 qualified_name.endswith('$' + param.replace('.', '$')) or
225 qualified_name == 'L' + param):
226 return prefix + JniParams.RemapClassName(qualified_name) + ';'
228 # Is it from an import? (e.g. referecing Class from import pkg.Class;
229 # note that referencing an inner class Inner from import pkg.Class.Inner
231 for qualified_name in JniParams._imports:
232 if qualified_name.endswith('/' + param):
233 # Ensure it's not an inner class.
234 components = qualified_name.split('/')
235 if len(components) > 2 and components[-2][0].isupper():
236 raise SyntaxError('Inner class (%s) can not be imported '
237 'and used by JNI (%s). Please import the outer '
238 'class and use Outer.Inner instead.' %
239 (qualified_name, param))
240 return prefix + JniParams.RemapClassName(qualified_name) + ';'
242 # Is it an inner class from an outer class import? (e.g. referencing
243 # Class.Inner from import pkg.Class).
245 components = param.split('.')
246 outer = '/'.join(components[:-1])
247 inner = components[-1]
248 for qualified_name in JniParams._imports:
249 if qualified_name.endswith('/' + outer):
250 return (prefix + JniParams.RemapClassName(qualified_name) +
252 raise SyntaxError('Inner class (%s) can not be '
253 'used directly by JNI. Please import the outer '
256 (param, JniParams._package.replace('/', '.'),
257 outer.replace('/', '.')))
259 JniParams._CheckImplicitImports(param)
261 # Type not found, falling back to same package as this class.
262 return (prefix + 'L' +
263 JniParams.RemapClassName(JniParams._package + '/' + param) + ';')
266 def _CheckImplicitImports(param):
267 # Ensure implicit imports, such as java.lang.*, are not being treated
268 # as being in the same package.
269 if not JniParams._implicit_imports:
270 # This file was generated from android.jar and lists
271 # all classes that are implicitly imported.
272 with file(os.path.join(os.path.dirname(sys.argv[0]),
273 'android_jar.classes'), 'r') as f:
274 JniParams._implicit_imports = f.readlines()
275 for implicit_import in JniParams._implicit_imports:
276 implicit_import = implicit_import.strip().replace('.class', '')
277 implicit_import = implicit_import.replace('/', '.')
278 if implicit_import.endswith('.' + param):
279 raise SyntaxError('Ambiguous class (%s) can not be used directly '
280 'by JNI.\nPlease import it, probably:\n\n'
282 (param, implicit_import))
286 def Signature(params, returns, wrap):
287 """Returns the JNI signature for the given datatypes."""
289 items += [JniParams.JavaToJni(param.datatype) for param in params]
291 items += [JniParams.JavaToJni(returns)]
293 return '\n' + '\n'.join(['"' + item + '"' for item in items])
295 return '"' + ''.join(items) + '"'
299 """Parses the params into a list of Param objects."""
303 for p in [p.strip() for p in params.split(',')]:
306 items.remove('final')
309 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
315 def RemapClassName(class_name):
316 """Remaps class names using the jarjar mapping table."""
317 for old, new in JniParams._remappings:
318 if old in class_name:
319 return class_name.replace(old, new, 1)
323 def SetJarJarMappings(mappings):
324 """Parse jarjar mappings from a string."""
325 JniParams._remappings = []
326 for line in mappings.splitlines():
327 keyword, src, dest = line.split()
328 if keyword != 'rule':
330 assert src.endswith('.**')
331 src = src[:-2].replace('.', '/')
332 dest = dest.replace('.', '/')
333 if dest.endswith('@0'):
334 JniParams._remappings.append((src, dest[:-2] + src))
336 assert dest.endswith('@1')
337 JniParams._remappings.append((src, dest[:-2]))
340 def ExtractJNINamespace(contents):
341 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
342 m = re.findall(re_jni_namespace, contents)
348 def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
349 re_package = re.compile('.*?package (.*?);')
350 matches = re.findall(re_package, contents)
352 raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
353 return (matches[0].replace('.', '/') + '/' +
354 os.path.splitext(os.path.basename(java_file_name))[0])
357 def ExtractNatives(contents, ptr_type):
358 """Returns a list of dict containing information about a native method."""
359 contents = contents.replace('\n', '')
361 re_native = re.compile(r'(@NativeClassQualifiedName'
362 '\(\"(?P<native_class_name>.*?)\"\))?\s*'
363 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\)))?\s*'
364 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*?native '
365 '(?P<return_type>\S*?) '
366 '(?P<name>native\w+?)\((?P<params>.*?)\);')
367 for match in re.finditer(re_native, contents):
368 native = NativeMethod(
369 static='static' in match.group('qualifiers'),
370 java_class_name=match.group('java_class_name'),
371 native_class_name=match.group('native_class_name'),
372 return_type=match.group('return_type'),
373 name=match.group('name').replace('native', ''),
374 params=JniParams.Parse(match.group('params')),
380 def GetStaticCastForReturnType(return_type):
381 type_map = { 'String' : 'jstring',
382 'java/lang/String' : 'jstring',
383 'boolean[]': 'jbooleanArray',
384 'byte[]': 'jbyteArray',
385 'char[]': 'jcharArray',
386 'short[]': 'jshortArray',
387 'int[]': 'jintArray',
388 'long[]': 'jlongArray',
389 'double[]': 'jdoubleArray' }
390 ret = type_map.get(return_type, None)
393 if return_type.endswith('[]'):
394 return 'jobjectArray'
398 def GetEnvCall(is_constructor, is_static, return_type):
399 """Maps the types availabe via env->Call__Method."""
402 env_call_map = {'boolean': 'Boolean',
413 call = env_call_map.get(return_type, 'Object')
415 call = 'Static' + call
416 return 'Call' + call + 'Method'
419 def GetMangledParam(datatype):
420 """Returns a mangled identifier for the datatype."""
421 if len(datatype) <= 2:
422 return datatype.replace('[', 'A')
424 for i in range(1, len(datatype)):
428 elif c.isupper() or datatype[i - 1] in ['/', 'L']:
433 def GetMangledMethodName(name, params, return_type):
434 """Returns a mangled method name for the given signature.
436 The returned name can be used as a C identifier and will be unique for all
437 valid overloads of the same method.
441 params: list of Param.
448 for datatype in [return_type] + [x.datatype for x in params]:
449 mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))]
450 mangled_name = name + '_'.join(mangled_items)
451 assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
455 def MangleCalledByNatives(called_by_natives):
456 """Mangles all the overloads from the call_by_natives list."""
457 method_counts = collections.defaultdict(
458 lambda: collections.defaultdict(lambda: 0))
459 for called_by_native in called_by_natives:
460 java_class_name = called_by_native.java_class_name
461 name = called_by_native.name
462 method_counts[java_class_name][name] += 1
463 for called_by_native in called_by_natives:
464 java_class_name = called_by_native.java_class_name
465 method_name = called_by_native.name
466 method_id_var_name = method_name
467 if method_counts[java_class_name][method_name] > 1:
468 method_id_var_name = GetMangledMethodName(method_name,
469 called_by_native.params,
470 called_by_native.return_type)
471 called_by_native.method_id_var_name = method_id_var_name
472 return called_by_natives
475 # Regex to match the JNI return types that should be included in a
476 # ScopedJavaLocalRef.
477 RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array')
479 # Regex to match a string like "@CalledByNative public void foo(int bar)".
480 RE_CALLED_BY_NATIVE = re.compile(
481 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
482 '\s+(?P<prefix>[\w ]*?)'
483 '\s*(?P<return_type>\S+?)'
485 '\s*\((?P<params>[^\)]*)\)')
488 def ExtractCalledByNatives(contents):
489 """Parses all methods annotated with @CalledByNative.
492 contents: the contents of the java file.
495 A list of dict with information about the annotated methods.
496 TODO(bulach): return a CalledByNative object.
499 ParseError: if unable to parse.
501 called_by_natives = []
502 for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
503 called_by_natives += [CalledByNative(
505 unchecked='Unchecked' in match.group('Unchecked'),
506 static='static' in match.group('prefix'),
507 java_class_name=match.group('annotation') or '',
508 return_type=match.group('return_type'),
509 name=match.group('name'),
510 params=JniParams.Parse(match.group('params')))]
511 # Check for any @CalledByNative occurrences that weren't matched.
512 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
513 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
514 if '@CalledByNative' in line1:
515 raise ParseError('could not parse @CalledByNative method signature',
517 return MangleCalledByNatives(called_by_natives)
520 class JNIFromJavaP(object):
521 """Uses 'javap' to parse a .class file and generate the JNI header file."""
523 def __init__(self, contents, options):
524 self.contents = contents
525 self.namespace = options.namespace
526 for line in contents:
527 class_name = re.match(
528 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
531 self.fully_qualified_class = class_name.group('class_name')
533 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
534 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
535 # away the <...> and use the raw class name that Java 6 would've given us.
536 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
537 JniParams.SetFullyQualifiedClass(self.fully_qualified_class)
538 self.java_class_name = self.fully_qualified_class.split('/')[-1]
539 if not self.namespace:
540 self.namespace = 'JNI_' + self.java_class_name
541 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
542 '\((?P<params>.*?)\)')
543 self.called_by_natives = []
544 for lineno, content in enumerate(contents[2:], 2):
545 match = re.match(re_method, content)
548 self.called_by_natives += [CalledByNative(
551 static='static' in match.group('prefix'),
553 return_type=match.group('return_type').replace('.', '/'),
554 name=match.group('name'),
555 params=JniParams.Parse(match.group('params').replace('.', '/')),
556 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
557 re_constructor = re.compile('(.*?)public ' +
558 self.fully_qualified_class.replace('/', '.') +
559 '\((?P<params>.*?)\)')
560 for lineno, content in enumerate(contents[2:], 2):
561 match = re.match(re_constructor, content)
564 self.called_by_natives += [CalledByNative(
569 return_type=self.fully_qualified_class,
571 params=JniParams.Parse(match.group('params').replace('.', '/')),
572 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
573 is_constructor=True)]
574 self.called_by_natives = MangleCalledByNatives(self.called_by_natives)
576 self.constant_fields = []
577 re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
578 re_constant_field_value = re.compile(
579 '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)')
580 for lineno, content in enumerate(contents[2:], 2):
581 match = re.match(re_constant_field, content)
584 value = re.match(re_constant_field_value, contents[lineno + 2])
586 value = re.match(re_constant_field_value, contents[lineno + 3])
588 self.constant_fields.append(
589 ConstantField(name=match.group('name'),
590 value=value.group('value')))
592 self.inl_header_file_generator = InlHeaderFileGenerator(
593 self.namespace, self.fully_qualified_class, [],
594 self.called_by_natives, self.constant_fields, options)
596 def GetContent(self):
597 return self.inl_header_file_generator.GetContent()
600 def CreateFromClass(class_file, options):
601 class_name = os.path.splitext(os.path.basename(class_file))[0]
602 p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
604 cwd=os.path.dirname(class_file),
605 stdout=subprocess.PIPE,
606 stderr=subprocess.PIPE)
607 stdout, _ = p.communicate()
608 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
609 return jni_from_javap
612 class JNIFromJavaSource(object):
613 """Uses the given java source file to generate the JNI header file."""
615 # Match single line comments, multiline comments, character literals, and
616 # double-quoted strings.
617 _comment_remover_regex = re.compile(
618 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
619 re.DOTALL | re.MULTILINE)
621 def __init__(self, contents, fully_qualified_class, options):
622 contents = self._RemoveComments(contents)
623 JniParams.SetFullyQualifiedClass(fully_qualified_class)
624 JniParams.ExtractImportsAndInnerClasses(contents)
625 jni_namespace = ExtractJNINamespace(contents) or options.namespace
626 natives = ExtractNatives(contents, options.ptr_type)
627 called_by_natives = ExtractCalledByNatives(contents)
628 if len(natives) == 0 and len(called_by_natives) == 0:
629 raise SyntaxError('Unable to find any JNI methods for %s.' %
630 fully_qualified_class)
631 inl_header_file_generator = InlHeaderFileGenerator(
632 jni_namespace, fully_qualified_class, natives, called_by_natives,
634 self.content = inl_header_file_generator.GetContent()
637 def _RemoveComments(cls, contents):
638 # We need to support both inline and block comments, and we need to handle
639 # strings that contain '//' or '/*'.
640 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
641 # parser. Maybe we could ditch JNIFromJavaSource and just always use
642 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
643 # http://code.google.com/p/chromium/issues/detail?id=138941
645 # Replace matches that are comments with nothing; return literals/strings
648 if s.startswith('/'):
652 return cls._comment_remover_regex.sub(replacer, contents)
654 def GetContent(self):
658 def CreateFromFile(java_file_name, options):
659 contents = file(java_file_name).read()
660 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
662 return JNIFromJavaSource(contents, fully_qualified_class, options)
665 class InlHeaderFileGenerator(object):
666 """Generates an inline header file for JNI integration."""
668 def __init__(self, namespace, fully_qualified_class, natives,
669 called_by_natives, constant_fields, options):
670 self.namespace = namespace
671 self.fully_qualified_class = fully_qualified_class
672 self.class_name = self.fully_qualified_class.split('/')[-1]
673 self.natives = natives
674 self.called_by_natives = called_by_natives
675 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
676 self.constant_fields = constant_fields
677 self.options = options
678 self.init_native = self.ExtractInitNative(options)
680 def ExtractInitNative(self, options):
681 for native in self.natives:
682 if options.jni_init_native_name == 'native' + native.name:
683 self.natives.remove(native)
687 def GetContent(self):
688 """Returns the content of the JNI binding file."""
689 template = Template("""\
690 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
691 // Use of this source code is governed by a BSD-style license that can be
692 // found in the LICENSE file.
695 // This file is autogenerated by
698 // ${FULLY_QUALIFIED_CLASS}
700 #ifndef ${HEADER_GUARD}
701 #define ${HEADER_GUARD}
707 #include "base/android/jni_int_wrapper.h"
709 // Step 1: forward declarations.
711 $CLASS_PATH_DEFINITIONS
712 $METHOD_ID_DEFINITIONS
716 $FORWARD_DECLARATIONS
720 // Step 2: method stubs.
723 // Step 3: RegisterNatives.
727 $JNI_REGISTER_NATIVES
728 #endif // ${HEADER_GUARD}
731 'SCRIPT_NAME': self.options.script_name,
732 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
733 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
734 'METHOD_ID_DEFINITIONS': self.GetMethodIDDefinitionsString(),
735 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(),
736 'CONSTANT_FIELDS': self.GetConstantFieldsString(),
737 'METHOD_STUBS': self.GetMethodStubsString(),
738 'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
739 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(),
740 'REGISTER_NATIVES': self.GetRegisterNativesString(),
741 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
742 'HEADER_GUARD': self.header_guard,
743 'INCLUDES': self.GetIncludesString(),
744 'JNI_REGISTER_NATIVES': self.GetJNIRegisterNativesString()
746 return WrapOutput(template.substitute(values))
748 def GetClassPathDefinitionsString(self):
750 ret += [self.GetClassPathDefinitions()]
751 return '\n'.join(ret)
753 def GetMethodIDDefinitionsString(self):
754 """Returns the definition of method ids for the called by native methods."""
755 if not self.options.eager_called_by_natives:
757 template = Template("""\
758 jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = NULL;""")
760 for called_by_native in self.called_by_natives:
762 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
763 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
765 ret += [template.substitute(values)]
766 return '\n'.join(ret)
768 def GetForwardDeclarationsString(self):
770 for native in self.natives:
771 if native.type != 'method':
772 ret += [self.GetForwardDeclaration(native)]
773 return '\n'.join(ret)
775 def GetConstantFieldsString(self):
776 if not self.constant_fields:
778 ret = ['enum Java_%s_constant_fields {' % self.class_name]
779 for c in self.constant_fields:
780 ret += [' %s = %s,' % (c.name, c.value)]
782 return '\n'.join(ret)
784 def GetMethodStubsString(self):
785 """Returns the code corresponding to method stubs."""
787 for native in self.natives:
788 if native.type == 'method':
789 ret += [self.GetNativeMethodStubString(native)]
790 if self.options.eager_called_by_natives:
791 ret += self.GetEagerCalledByNativeMethodStubs()
793 ret += self.GetLazyCalledByNativeMethodStubs()
794 return '\n'.join(ret)
796 def GetLazyCalledByNativeMethodStubs(self):
797 return [self.GetLazyCalledByNativeMethodStub(called_by_native)
798 for called_by_native in self.called_by_natives]
800 def GetEagerCalledByNativeMethodStubs(self):
802 if self.called_by_natives:
803 ret += ['namespace {']
804 for called_by_native in self.called_by_natives:
805 ret += [self.GetEagerCalledByNativeMethodStub(called_by_native)]
806 ret += ['} // namespace']
809 def GetIncludesString(self):
810 if not self.options.includes:
812 includes = self.options.includes.split(',')
813 return '\n'.join('#include "%s"' % x for x in includes)
815 def GetKMethodsString(self, clazz):
817 for native in self.natives:
818 if (native.java_class_name == clazz or
819 (not native.java_class_name and clazz == self.class_name)):
820 ret += [self.GetKMethodArrayEntry(native)]
821 return '\n'.join(ret)
823 def SubstituteNativeMethods(self, template):
824 """Substitutes JAVA_CLASS and KMETHODS in the provided template."""
826 all_classes = self.GetUniqueClasses(self.natives)
827 all_classes[self.class_name] = self.fully_qualified_class
828 for clazz in all_classes:
829 kmethods = self.GetKMethodsString(clazz)
831 values = {'JAVA_CLASS': clazz,
832 'KMETHODS': kmethods}
833 ret += [template.substitute(values)]
834 if not ret: return ''
835 return '\n' + '\n'.join(ret)
837 def GetJNINativeMethodsString(self):
838 """Returns the implementation of the array of native methods."""
839 template = Template("""\
840 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
844 return self.SubstituteNativeMethods(template)
846 def GetRegisterCalledByNativesImplString(self):
847 """Returns the code for registering the called by native methods."""
848 if not self.options.eager_called_by_natives:
850 template = Template("""\
851 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = ${GET_METHOD_ID_IMPL}
852 if (g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} == NULL) {
857 for called_by_native in self.called_by_natives:
859 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
860 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
861 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native),
863 ret += [template.substitute(values)]
864 return '\n'.join(ret)
866 def GetRegisterNativesString(self):
867 """Returns the code for RegisterNatives."""
868 template = Template("""\
869 ${REGISTER_NATIVES_SIGNATURE} {
876 signature = 'static bool RegisterNativesImpl(JNIEnv* env'
878 signature += ', jclass clazz)'
882 natives = self.GetRegisterNativesImplString()
883 called_by_natives = self.GetRegisterCalledByNativesImplString()
884 values = {'REGISTER_NATIVES_SIGNATURE': signature,
885 'CLASSES': self.GetFindClasses(),
887 'CALLED_BY_NATIVES': called_by_natives,
889 return template.substitute(values)
891 def GetRegisterNativesImplString(self):
892 """Returns the shared implementation for RegisterNatives."""
893 template = Template("""\
894 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
896 if (env->RegisterNatives(g_${JAVA_CLASS}_clazz,
897 kMethods${JAVA_CLASS},
898 kMethods${JAVA_CLASS}Size) < 0) {
899 jni_generator::HandleRegistrationError(
900 env, g_${JAVA_CLASS}_clazz, __FILE__);
904 return self.SubstituteNativeMethods(template)
906 def GetJNIRegisterNativesString(self):
907 """Returns the implementation for the JNI registration of native methods."""
908 if not self.init_native:
911 template = Template("""\
912 extern "C" JNIEXPORT bool JNICALL
913 Java_${FULLY_QUALIFIED_CLASS}_${INIT_NATIVE_NAME}(JNIEnv* env, jclass clazz) {
914 return ${NAMESPACE}RegisterNativesImpl(env, clazz);
917 fully_qualified_class = self.fully_qualified_class.replace('/', '_')
920 namespace = self.namespace + '::'
921 values = {'FULLY_QUALIFIED_CLASS': fully_qualified_class,
922 'INIT_NATIVE_NAME': 'native' + self.init_native.name,
923 'NAMESPACE': namespace,
924 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString()
926 return template.substitute(values)
928 def GetOpenNamespaceString(self):
930 all_namespaces = ['namespace %s {' % ns
931 for ns in self.namespace.split('::')]
932 return '\n'.join(all_namespaces)
935 def GetCloseNamespaceString(self):
937 all_namespaces = ['} // namespace %s' % ns
938 for ns in self.namespace.split('::')]
939 all_namespaces.reverse()
940 return '\n'.join(all_namespaces) + '\n'
943 def GetJNIFirstParam(self, native):
945 if native.type == 'method':
946 ret = ['jobject jcaller']
947 elif native.type == 'function':
949 ret = ['jclass jcaller']
951 ret = ['jobject jcaller']
954 def GetParamsInDeclaration(self, native):
955 """Returns the params for the stub declaration.
958 native: the native dictionary describing the method.
961 A string containing the params.
963 return ',\n '.join(self.GetJNIFirstParam(native) +
964 [JavaDataTypeToC(param.datatype) + ' ' +
966 for param in native.params])
968 def GetCalledByNativeParamsInDeclaration(self, called_by_native):
970 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
972 for param in called_by_native.params])
974 def GetForwardDeclaration(self, native):
975 template = Template("""
976 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
978 values = {'RETURN': JavaDataTypeToC(native.return_type),
980 'PARAMS': self.GetParamsInDeclaration(native)}
981 return template.substitute(values)
983 def GetNativeMethodStubString(self, native):
984 """Returns stubs for native methods."""
985 template = Template("""\
986 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {
987 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
988 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
989 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
993 if not self.options.pure_native_methods:
994 params = ['env', 'jcaller']
995 params_in_call = ', '.join(params + [p.name for p in native.params[1:]])
997 return_type = JavaDataTypeToC(native.return_type)
998 optional_error_return = JavaReturnValueToC(native.return_type)
999 if optional_error_return:
1000 optional_error_return = ', ' + optional_error_return
1002 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1003 post_call = '.Release()'
1005 'RETURN': return_type,
1006 'OPTIONAL_ERROR_RETURN': optional_error_return,
1007 'NAME': native.name,
1008 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native),
1009 'PARAM0_NAME': native.params[0].name,
1010 'P0_TYPE': native.p0_type,
1011 'PARAMS_IN_CALL': params_in_call,
1012 'POST_CALL': post_call
1014 return template.substitute(values)
1016 def GetArgument(self, param):
1017 return ('as_jint(' + param.name + ')'
1018 if param.datatype == 'int' else param.name)
1020 def GetArgumentsInCall(self, params):
1021 """Return a string of arguments to call from native into Java"""
1022 return [self.GetArgument(p) for p in params]
1024 def GetCalledByNativeValues(self, called_by_native):
1025 """Fills in necessary values for the CalledByNative methods."""
1026 if called_by_native.static or called_by_native.is_constructor:
1027 first_param_in_declaration = ''
1028 first_param_in_call = ('g_%s_clazz' %
1029 (called_by_native.java_class_name or
1032 first_param_in_declaration = ', jobject obj'
1033 first_param_in_call = 'obj'
1034 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
1036 if params_in_declaration:
1037 params_in_declaration = ', ' + params_in_declaration
1038 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
1040 params_in_call = ', ' + params_in_call
1043 if called_by_native.static_cast:
1044 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
1046 check_exception = ''
1047 if not called_by_native.unchecked:
1048 check_exception = 'jni_generator::CheckException(env);'
1049 return_type = JavaDataTypeToC(called_by_native.return_type)
1050 optional_error_return = JavaReturnValueToC(called_by_native.return_type)
1051 if optional_error_return:
1052 optional_error_return = ', ' + optional_error_return
1053 return_declaration = ''
1055 if return_type != 'void':
1056 pre_call = ' ' + pre_call
1057 return_declaration = return_type + ' ret ='
1058 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1059 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
1060 return_clause = 'return ' + return_type + '(env, ret);'
1062 return_clause = 'return ret;'
1064 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
1065 'RETURN_TYPE': return_type,
1066 'OPTIONAL_ERROR_RETURN': optional_error_return,
1067 'RETURN_DECLARATION': return_declaration,
1068 'RETURN_CLAUSE': return_clause,
1069 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
1070 'PARAMS_IN_DECLARATION': params_in_declaration,
1071 'PRE_CALL': pre_call,
1072 'POST_CALL': post_call,
1073 'ENV_CALL': called_by_native.env_call,
1074 'FIRST_PARAM_IN_CALL': first_param_in_call,
1075 'PARAMS_IN_CALL': params_in_call,
1076 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1077 'CHECK_EXCEPTION': check_exception,
1078 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native)
1081 def GetEagerCalledByNativeMethodStub(self, called_by_native):
1082 """Returns the implementation of the called by native method."""
1083 template = Template("""
1084 static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\
1085 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION}) {
1086 ${RETURN_DECLARATION}${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1087 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL};
1090 values = self.GetCalledByNativeValues(called_by_native)
1091 return template.substitute(values)
1093 def GetLazyCalledByNativeMethodStub(self, called_by_native):
1094 """Returns a string."""
1095 function_signature_template = Template("""\
1096 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
1097 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
1098 function_header_template = Template("""\
1099 ${FUNCTION_SIGNATURE} {""")
1100 function_header_with_unused_template = Template("""\
1101 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
1102 ${FUNCTION_SIGNATURE} {""")
1103 template = Template("""
1104 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
1106 /* Must call RegisterNativesImpl() */
1107 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
1108 g_${JAVA_CLASS}_clazz${OPTIONAL_ERROR_RETURN});
1109 jmethodID method_id =
1110 ${GET_METHOD_ID_IMPL}
1111 ${RETURN_DECLARATION}
1112 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1113 method_id${PARAMS_IN_CALL})${POST_CALL};
1117 values = self.GetCalledByNativeValues(called_by_native)
1118 values['FUNCTION_SIGNATURE'] = (
1119 function_signature_template.substitute(values))
1120 if called_by_native.system_class:
1121 values['FUNCTION_HEADER'] = (
1122 function_header_with_unused_template.substitute(values))
1124 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
1125 return template.substitute(values)
1127 def GetKMethodArrayEntry(self, native):
1128 template = Template("""\
1129 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""")
1130 values = {'NAME': native.name,
1131 'JNI_SIGNATURE': JniParams.Signature(native.params,
1134 return template.substitute(values)
1136 def GetUniqueClasses(self, origin):
1137 ret = {self.class_name: self.fully_qualified_class}
1138 for entry in origin:
1139 class_name = self.class_name
1140 jni_class_path = self.fully_qualified_class
1141 if entry.java_class_name:
1142 class_name = entry.java_class_name
1143 jni_class_path = self.fully_qualified_class + '$' + class_name
1144 ret[class_name] = jni_class_path
1147 def GetClassPathDefinitions(self):
1148 """Returns the ClassPath constants."""
1150 template = Template("""\
1151 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
1152 native_classes = self.GetUniqueClasses(self.natives)
1153 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
1154 all_classes = native_classes
1155 all_classes.update(called_by_native_classes)
1156 for clazz in all_classes:
1158 'JAVA_CLASS': clazz,
1159 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]),
1161 ret += [template.substitute(values)]
1163 for clazz in called_by_native_classes:
1164 template = Template("""\
1165 // Leaking this jclass as we cannot use LazyInstance from some threads.
1166 jclass g_${JAVA_CLASS}_clazz = NULL;""")
1168 'JAVA_CLASS': clazz,
1170 ret += [template.substitute(values)]
1171 return '\n'.join(ret)
1173 def GetFindClasses(self):
1174 """Returns the imlementation of FindClass for all known classes."""
1175 if self.init_native:
1176 template = Template("""\
1177 g_${JAVA_CLASS}_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));""")
1179 template = Template("""\
1180 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
1181 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
1183 for clazz in self.GetUniqueClasses(self.called_by_natives):
1184 values = {'JAVA_CLASS': clazz}
1185 ret += [template.substitute(values)]
1186 return '\n'.join(ret)
1188 def GetMethodIDImpl(self, called_by_native):
1189 """Returns the implementation of GetMethodID."""
1190 if self.options.eager_called_by_natives:
1191 template = Template("""\
1192 env->Get${STATIC_METHOD_PART}MethodID(
1193 g_${JAVA_CLASS}_clazz,
1194 "${JNI_NAME}", ${JNI_SIGNATURE});""")
1196 template = Template("""\
1197 base::android::MethodID::LazyGet<
1198 base::android::MethodID::TYPE_${STATIC}>(
1199 env, g_${JAVA_CLASS}_clazz,
1202 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
1204 jni_name = called_by_native.name
1205 jni_return_type = called_by_native.return_type
1206 if called_by_native.is_constructor:
1208 jni_return_type = 'void'
1209 if called_by_native.signature:
1210 signature = called_by_native.signature
1212 signature = JniParams.Signature(called_by_native.params,
1216 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
1217 'JNI_NAME': jni_name,
1218 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1219 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
1220 'STATIC_METHOD_PART': 'Static' if called_by_native.static else '',
1221 'JNI_SIGNATURE': signature,
1223 return template.substitute(values)
1226 def WrapOutput(output):
1228 for line in output.splitlines():
1229 # Do not wrap lines under 80 characters or preprocessor directives.
1230 if len(line) < 80 or line.lstrip()[:1] == '#':
1231 stripped = line.rstrip()
1232 if len(ret) == 0 or len(ret[-1]) or len(stripped):
1233 ret.append(stripped)
1235 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
1236 subsequent_indent = first_line_indent + ' ' * 4
1237 if line.startswith('//'):
1238 subsequent_indent = '//' + subsequent_indent
1239 wrapper = textwrap.TextWrapper(width=80,
1240 subsequent_indent=subsequent_indent,
1241 break_long_words=False)
1242 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
1244 return '\n'.join(ret)
1247 def ExtractJarInputFile(jar_file, input_file, out_dir):
1248 """Extracts input file from jar and returns the filename.
1250 The input file is extracted to the same directory that the generated jni
1251 headers will be placed in. This is passed as an argument to script.
1254 jar_file: the jar file containing the input files to extract.
1255 input_files: the list of files to extract from the jar file.
1256 out_dir: the name of the directories to extract to.
1259 the name of extracted input file.
1261 jar_file = zipfile.ZipFile(jar_file)
1263 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
1265 os.makedirs(out_dir)
1266 except OSError as e:
1267 if e.errno != errno.EEXIST:
1269 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
1270 with open(extracted_file_name, 'w') as outfile:
1271 outfile.write(jar_file.read(input_file))
1273 return extracted_file_name
1276 def GenerateJNIHeader(input_file, output_file, options):
1278 if os.path.splitext(input_file)[1] == '.class':
1279 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
1280 content = jni_from_javap.GetContent()
1282 jni_from_java_source = JNIFromJavaSource.CreateFromFile(
1283 input_file, options)
1284 content = jni_from_java_source.GetContent()
1285 except ParseError, e:
1289 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
1290 os.makedirs(os.path.dirname(os.path.abspath(output_file)))
1291 if options.optimize_generation and os.path.exists(output_file):
1292 with file(output_file, 'r') as f:
1293 existing_content = f.read()
1294 if existing_content == content:
1296 with file(output_file, 'w') as f:
1302 def GetScriptName():
1303 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
1305 for idx, value in enumerate(script_components):
1306 if value == 'base' or value == 'third_party':
1309 return os.sep.join(script_components[base_index:])
1313 usage = """usage: %prog [OPTIONS]
1314 This script will parse the given java source code extracting the native
1315 declarations and print the header file to stdout (or a file).
1316 See SampleForTests.java for more details.
1318 option_parser = optparse.OptionParser(usage=usage)
1319 option_parser.add_option('-j', dest='jar_file',
1320 help='Extract the list of input files from'
1321 ' a specified jar file.'
1322 ' Uses javap to extract the methods from a'
1323 ' pre-compiled class. --input should point'
1324 ' to pre-compiled Java .class files.')
1325 option_parser.add_option('-n', dest='namespace',
1326 help='Uses as a namespace in the generated header '
1327 'instead of the javap class name, or when there is '
1328 'no JNINamespace annotation in the java source.')
1329 option_parser.add_option('--input_file',
1330 help='Single input file name. The output file name '
1331 'will be derived from it. Must be used with '
1333 option_parser.add_option('--output_dir',
1334 help='The output directory. Must be used with '
1336 option_parser.add_option('--optimize_generation', type="int",
1337 default=0, help='Whether we should optimize JNI '
1338 'generation by not regenerating files if they have '
1340 option_parser.add_option('--jarjar',
1341 help='Path to optional jarjar rules file.')
1342 option_parser.add_option('--script_name', default=GetScriptName(),
1343 help='The name of this script in the generated '
1345 option_parser.add_option('--includes',
1346 help='The comma-separated list of header files to '
1347 'include in the generated header.')
1348 option_parser.add_option('--pure_native_methods',
1349 action='store_true', dest='pure_native_methods',
1350 help='When true, the native methods will be called '
1351 'without any JNI-specific arguments.')
1352 option_parser.add_option('--ptr_type', default='int',
1353 type='choice', choices=['int', 'long'],
1354 help='The type used to represent native pointers in '
1355 'Java code. For 32-bit, use int; '
1356 'for 64-bit, use long.')
1357 option_parser.add_option('--jni_init_native_name', default='',
1358 help='The name of the JNI registration method that '
1359 'is used to initialize all native methods. If a '
1360 'method with this name is not present in the Java '
1361 'source file, setting this option is a no-op. When '
1362 'a method with this name is found however, the '
1363 'naming convention Java_<packageName>_<className> '
1364 'will limit the initialization to only the '
1366 option_parser.add_option('--eager_called_by_natives',
1367 action='store_true', dest='eager_called_by_natives',
1368 help='When true, the called-by-native methods will '
1369 'be initialized in a non-atomic way.')
1370 option_parser.add_option('--cpp', default='cpp',
1371 help='The path to cpp command.')
1372 option_parser.add_option('--javap', default='javap',
1373 help='The path to javap command.')
1374 options, args = option_parser.parse_args(argv)
1375 if options.jar_file:
1376 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1378 elif options.input_file:
1379 input_file = options.input_file
1381 option_parser.print_help()
1382 print '\nError: Must specify --jar_file or --input_file.'
1385 if options.output_dir:
1386 root_name = os.path.splitext(os.path.basename(input_file))[0]
1387 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
1389 with open(options.jarjar) as f:
1390 JniParams.SetJarJarMappings(f.read())
1391 GenerateJNIHeader(input_file, output_file, options)
1394 if __name__ == '__main__':
1395 sys.exit(main(sys.argv))