Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / base / android / jni_generator / jni_generator.py
blobf71f5536fde4ae2ab6e7c0ba9feffb7cea1900eb
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Extracts native methods from a Java file and generates the JNI bindings.
7 If you change this, please run and update the tests."""
9 import collections
10 import errno
11 import optparse
12 import os
13 import re
14 import string
15 from string import Template
16 import subprocess
17 import sys
18 import textwrap
19 import zipfile
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
30 def __str__(self):
31 context = '\n'.join(self.context_lines)
32 return '***\nERROR: %s\n\n%s\n***' % (self.description, context)
35 class Param(object):
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']
52 if self.params:
53 assert type(self.params) is list
54 assert type(self.params[0]) is Param
55 if (self.params and
56 self.params[0].datatype == 'int' and
57 self.params[0].name.startswith('native')):
58 self.type = 'method'
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']
62 else:
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.is_constructor = kwargs.get('is_constructor', False)
80 self.env_call = GetEnvCall(self.is_constructor, self.static,
81 self.return_type)
82 self.static_cast = GetStaticCastForReturnType(self.return_type)
85 def JavaDataTypeToC(java_type):
86 """Returns a C datatype for the given java type."""
87 java_pod_type_map = {
88 'int': 'jint',
89 'byte': 'jbyte',
90 'char': 'jchar',
91 'short': 'jshort',
92 'boolean': 'jboolean',
93 'long': 'jlong',
94 'double': 'jdouble',
95 'float': 'jfloat',
97 java_type_map = {
98 'void': 'void',
99 'String': 'jstring',
100 'java/lang/String': 'jstring',
101 'Class': 'jclass',
102 'java/lang/Class': 'jclass',
105 if java_type in java_pod_type_map:
106 return java_pod_type_map[java_type]
107 elif java_type in java_type_map:
108 return java_type_map[java_type]
109 elif java_type.endswith('[]'):
110 if java_type[:-2] in java_pod_type_map:
111 return java_pod_type_map[java_type[:-2]] + 'Array'
112 return 'jobjectArray'
113 else:
114 return 'jobject'
117 class JniParams(object):
118 _imports = []
119 _fully_qualified_class = ''
120 _package = ''
121 _inner_classes = []
123 @staticmethod
124 def SetFullyQualifiedClass(fully_qualified_class):
125 JniParams._fully_qualified_class = 'L' + fully_qualified_class
126 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1])
128 @staticmethod
129 def ExtractImportsAndInnerClasses(contents):
130 contents = contents.replace('\n', '')
131 re_import = re.compile(r'import.*?(?P<class>\S*?);')
132 for match in re.finditer(re_import, contents):
133 JniParams._imports += ['L' + match.group('class').replace('.', '/')]
135 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W')
136 for match in re.finditer(re_inner, contents):
137 inner = match.group('name')
138 if not JniParams._fully_qualified_class.endswith(inner):
139 JniParams._inner_classes += [JniParams._fully_qualified_class + '$' +
140 inner]
142 @staticmethod
143 def JavaToJni(param):
144 """Converts a java param into a JNI signature type."""
145 pod_param_map = {
146 'int': 'I',
147 'boolean': 'Z',
148 'char': 'C',
149 'short': 'S',
150 'long': 'J',
151 'double': 'D',
152 'float': 'F',
153 'byte': 'B',
154 'void': 'V',
156 object_param_list = [
157 'Ljava/lang/Boolean',
158 'Ljava/lang/Integer',
159 'Ljava/lang/Long',
160 'Ljava/lang/Object',
161 'Ljava/lang/String',
162 'Ljava/lang/Class',
164 prefix = ''
165 # Array?
166 while param[-2:] == '[]':
167 prefix += '['
168 param = param[:-2]
169 # Generic?
170 if '<' in param:
171 param = param[:param.index('<')]
172 if param in pod_param_map:
173 return prefix + pod_param_map[param]
174 if '/' in param:
175 # Coming from javap, use the fully qualified param directly.
176 return prefix + 'L' + param + ';'
177 for qualified_name in (object_param_list +
178 [JniParams._fully_qualified_class] +
179 JniParams._inner_classes):
180 if (qualified_name.endswith('/' + param) or
181 qualified_name.endswith('$' + param.replace('.', '$')) or
182 qualified_name == 'L' + param):
183 return prefix + qualified_name + ';'
185 # Is it from an import? (e.g. referecing Class from import pkg.Class;
186 # note that referencing an inner class Inner from import pkg.Class.Inner
187 # is not supported).
188 for qualified_name in JniParams._imports:
189 if qualified_name.endswith('/' + param):
190 # Ensure it's not an inner class.
191 components = qualified_name.split('/')
192 if len(components) > 2 and components[-2][0].isupper():
193 raise SyntaxError('Inner class (%s) can not be imported '
194 'and used by JNI (%s). Please import the outer '
195 'class and use Outer.Inner instead.' %
196 (qualified_name, param))
197 return prefix + qualified_name + ';'
199 # Is it an inner class from an outer class import? (e.g. referencing
200 # Class.Inner from import pkg.Class).
201 if '.' in param:
202 components = param.split('.')
203 outer = '/'.join(components[:-1])
204 inner = components[-1]
205 for qualified_name in JniParams._imports:
206 if qualified_name.endswith('/' + outer):
207 return prefix + qualified_name + '$' + inner + ';'
209 # Type not found, falling back to same package as this class.
210 return prefix + 'L' + JniParams._package + '/' + param + ';'
212 @staticmethod
213 def Signature(params, returns, wrap):
214 """Returns the JNI signature for the given datatypes."""
215 items = ['(']
216 items += [JniParams.JavaToJni(param.datatype) for param in params]
217 items += [')']
218 items += [JniParams.JavaToJni(returns)]
219 if wrap:
220 return '\n' + '\n'.join(['"' + item + '"' for item in items])
221 else:
222 return '"' + ''.join(items) + '"'
224 @staticmethod
225 def Parse(params):
226 """Parses the params into a list of Param objects."""
227 if not params:
228 return []
229 ret = []
230 for p in [p.strip() for p in params.split(',')]:
231 items = p.split(' ')
232 if 'final' in items:
233 items.remove('final')
234 param = Param(
235 datatype=items[0],
236 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
238 ret += [param]
239 return ret
242 def ExtractJNINamespace(contents):
243 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
244 m = re.findall(re_jni_namespace, contents)
245 if not m:
246 return ''
247 return m[0]
250 def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
251 re_package = re.compile('.*?package (.*?);')
252 matches = re.findall(re_package, contents)
253 if not matches:
254 raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
255 return (matches[0].replace('.', '/') + '/' +
256 os.path.splitext(os.path.basename(java_file_name))[0])
259 def ExtractNatives(contents):
260 """Returns a list of dict containing information about a native method."""
261 contents = contents.replace('\n', '')
262 natives = []
263 re_native = re.compile(r'(@NativeClassQualifiedName'
264 '\(\"(?P<native_class_name>.*?)\"\))?\s*'
265 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\)))?\s*'
266 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*?native '
267 '(?P<return_type>\S*?) '
268 '(?P<name>\w+?)\((?P<params>.*?)\);')
269 for match in re.finditer(re_native, contents):
270 native = NativeMethod(
271 static='static' in match.group('qualifiers'),
272 java_class_name=match.group('java_class_name'),
273 native_class_name=match.group('native_class_name'),
274 return_type=match.group('return_type'),
275 name=match.group('name').replace('native', ''),
276 params=JniParams.Parse(match.group('params')))
277 natives += [native]
278 return natives
281 def GetStaticCastForReturnType(return_type):
282 type_map = { 'String' : 'jstring',
283 'java/lang/String' : 'jstring',
284 'boolean[]': 'jbooleanArray',
285 'byte[]': 'jbyteArray',
286 'char[]': 'jcharArray',
287 'short[]': 'jshortArray',
288 'int[]': 'jintArray',
289 'long[]': 'jlongArray',
290 'double[]': 'jdoubleArray' }
291 ret = type_map.get(return_type, None)
292 if ret:
293 return ret
294 if return_type.endswith('[]'):
295 return 'jobjectArray'
296 return None
299 def GetEnvCall(is_constructor, is_static, return_type):
300 """Maps the types availabe via env->Call__Method."""
301 if is_constructor:
302 return 'NewObject'
303 env_call_map = {'boolean': 'Boolean',
304 'byte': 'Byte',
305 'char': 'Char',
306 'short': 'Short',
307 'int': 'Int',
308 'long': 'Long',
309 'float': 'Float',
310 'void': 'Void',
311 'double': 'Double',
312 'Object': 'Object',
314 call = env_call_map.get(return_type, 'Object')
315 if is_static:
316 call = 'Static' + call
317 return 'Call' + call + 'Method'
320 def GetMangledParam(datatype):
321 """Returns a mangled identifier for the datatype."""
322 if len(datatype) <= 2:
323 return datatype.replace('[', 'A')
324 ret = ''
325 for i in range(1, len(datatype)):
326 c = datatype[i]
327 if c == '[':
328 ret += 'A'
329 elif c.isupper() or datatype[i - 1] in ['/', 'L']:
330 ret += c.upper()
331 return ret
334 def GetMangledMethodName(name, params, return_type):
335 """Returns a mangled method name for the given signature.
337 The returned name can be used as a C identifier and will be unique for all
338 valid overloads of the same method.
340 Args:
341 name: string.
342 params: list of Param.
343 return_type: string.
345 Returns:
346 A mangled name.
348 mangled_items = []
349 for datatype in [return_type] + [x.datatype for x in params]:
350 mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))]
351 mangled_name = name + '_'.join(mangled_items)
352 assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
353 return mangled_name
356 def MangleCalledByNatives(called_by_natives):
357 """Mangles all the overloads from the call_by_natives list."""
358 method_counts = collections.defaultdict(
359 lambda: collections.defaultdict(lambda: 0))
360 for called_by_native in called_by_natives:
361 java_class_name = called_by_native.java_class_name
362 name = called_by_native.name
363 method_counts[java_class_name][name] += 1
364 for called_by_native in called_by_natives:
365 java_class_name = called_by_native.java_class_name
366 method_name = called_by_native.name
367 method_id_var_name = method_name
368 if method_counts[java_class_name][method_name] > 1:
369 method_id_var_name = GetMangledMethodName(method_name,
370 called_by_native.params,
371 called_by_native.return_type)
372 called_by_native.method_id_var_name = method_id_var_name
373 return called_by_natives
376 # Regex to match the JNI return types that should be included in a
377 # ScopedJavaLocalRef.
378 RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array')
380 # Regex to match a string like "@CalledByNative public void foo(int bar)".
381 RE_CALLED_BY_NATIVE = re.compile(
382 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
383 '\s+(?P<prefix>[\w ]*?)'
384 '\s*(?P<return_type>\S+?)'
385 '\s+(?P<name>\w+)'
386 '\s*\((?P<params>[^\)]*)\)')
389 def ExtractCalledByNatives(contents):
390 """Parses all methods annotated with @CalledByNative.
392 Args:
393 contents: the contents of the java file.
395 Returns:
396 A list of dict with information about the annotated methods.
397 TODO(bulach): return a CalledByNative object.
399 Raises:
400 ParseError: if unable to parse.
402 called_by_natives = []
403 for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
404 called_by_natives += [CalledByNative(
405 system_class=False,
406 unchecked='Unchecked' in match.group('Unchecked'),
407 static='static' in match.group('prefix'),
408 java_class_name=match.group('annotation') or '',
409 return_type=match.group('return_type'),
410 name=match.group('name'),
411 params=JniParams.Parse(match.group('params')))]
412 # Check for any @CalledByNative occurrences that weren't matched.
413 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
414 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
415 if '@CalledByNative' in line1:
416 raise ParseError('could not parse @CalledByNative method signature',
417 line1, line2)
418 return MangleCalledByNatives(called_by_natives)
421 class JNIFromJavaP(object):
422 """Uses 'javap' to parse a .class file and generate the JNI header file."""
424 def __init__(self, contents, namespace):
425 self.contents = contents
426 self.namespace = namespace
427 self.fully_qualified_class = re.match(
428 '.*?(class|interface) (?P<class_name>.*?)( |{)',
429 contents[1]).group('class_name')
430 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
431 JniParams.SetFullyQualifiedClass(self.fully_qualified_class)
432 self.java_class_name = self.fully_qualified_class.split('/')[-1]
433 if not self.namespace:
434 self.namespace = 'JNI_' + self.java_class_name
435 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
436 '\((?P<params>.*?)\)')
437 self.called_by_natives = []
438 for content in contents[2:]:
439 match = re.match(re_method, content)
440 if not match:
441 continue
442 self.called_by_natives += [CalledByNative(
443 system_class=True,
444 unchecked=False,
445 static='static' in match.group('prefix'),
446 java_class_name='',
447 return_type=match.group('return_type').replace('.', '/'),
448 name=match.group('name'),
449 params=JniParams.Parse(match.group('params').replace('.', '/')))]
450 re_constructor = re.compile('.*? public ' +
451 self.fully_qualified_class.replace('/', '.') +
452 '\((?P<params>.*?)\)')
453 for content in contents[2:]:
454 match = re.match(re_constructor, content)
455 if not match:
456 continue
457 self.called_by_natives += [CalledByNative(
458 system_class=True,
459 unchecked=False,
460 static=False,
461 java_class_name='',
462 return_type=self.fully_qualified_class,
463 name='Constructor',
464 params=JniParams.Parse(match.group('params').replace('.', '/')),
465 is_constructor=True)]
466 self.called_by_natives = MangleCalledByNatives(self.called_by_natives)
467 self.inl_header_file_generator = InlHeaderFileGenerator(
468 self.namespace, self.fully_qualified_class, [], self.called_by_natives)
470 def GetContent(self):
471 return self.inl_header_file_generator.GetContent()
473 @staticmethod
474 def CreateFromClass(class_file, namespace):
475 class_name = os.path.splitext(os.path.basename(class_file))[0]
476 p = subprocess.Popen(args=['javap', class_name],
477 cwd=os.path.dirname(class_file),
478 stdout=subprocess.PIPE,
479 stderr=subprocess.PIPE)
480 stdout, _ = p.communicate()
481 jni_from_javap = JNIFromJavaP(stdout.split('\n'), namespace)
482 return jni_from_javap
485 class JNIFromJavaSource(object):
486 """Uses the given java source file to generate the JNI header file."""
488 def __init__(self, contents, fully_qualified_class):
489 contents = self._RemoveComments(contents)
490 JniParams.SetFullyQualifiedClass(fully_qualified_class)
491 JniParams.ExtractImportsAndInnerClasses(contents)
492 jni_namespace = ExtractJNINamespace(contents)
493 natives = ExtractNatives(contents)
494 called_by_natives = ExtractCalledByNatives(contents)
495 if len(natives) == 0 and len(called_by_natives) == 0:
496 raise SyntaxError('Unable to find any JNI methods for %s.' %
497 fully_qualified_class)
498 inl_header_file_generator = InlHeaderFileGenerator(
499 jni_namespace, fully_qualified_class, natives, called_by_natives)
500 self.content = inl_header_file_generator.GetContent()
502 def _RemoveComments(self, contents):
503 # We need to support both inline and block comments, and we need to handle
504 # strings that contain '//' or '/*'. Rather than trying to do all that with
505 # regexps, we just pipe the contents through the C preprocessor. We tell cpp
506 # the file has already been preprocessed, so it just removes comments and
507 # doesn't try to parse #include, #pragma etc.
509 # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java
510 # parser. Maybe we could ditch JNIFromJavaSource and just always use
511 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
512 # http://code.google.com/p/chromium/issues/detail?id=138941
513 p = subprocess.Popen(args=['cpp', '-fpreprocessed'],
514 stdin=subprocess.PIPE,
515 stdout=subprocess.PIPE,
516 stderr=subprocess.PIPE)
517 stdout, _ = p.communicate(contents)
518 return stdout
520 def GetContent(self):
521 return self.content
523 @staticmethod
524 def CreateFromFile(java_file_name):
525 contents = file(java_file_name).read()
526 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
527 contents)
528 return JNIFromJavaSource(contents, fully_qualified_class)
531 class InlHeaderFileGenerator(object):
532 """Generates an inline header file for JNI integration."""
534 def __init__(self, namespace, fully_qualified_class, natives,
535 called_by_natives):
536 self.namespace = namespace
537 self.fully_qualified_class = fully_qualified_class
538 self.class_name = self.fully_qualified_class.split('/')[-1]
539 self.natives = natives
540 self.called_by_natives = called_by_natives
541 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
543 def GetContent(self):
544 """Returns the content of the JNI binding file."""
545 template = Template("""\
546 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
547 // Use of this source code is governed by a BSD-style license that can be
548 // found in the LICENSE file.
551 // This file is autogenerated by
552 // ${SCRIPT_NAME}
553 // For
554 // ${FULLY_QUALIFIED_CLASS}
556 #ifndef ${HEADER_GUARD}
557 #define ${HEADER_GUARD}
559 #include <jni.h>
561 #include "base/android/jni_android.h"
562 #include "base/android/scoped_java_ref.h"
563 #include "base/basictypes.h"
564 #include "base/logging.h"
566 using base::android::ScopedJavaLocalRef;
568 // Step 1: forward declarations.
569 namespace {
570 $CLASS_PATH_DEFINITIONS
571 } // namespace
573 $OPEN_NAMESPACE
574 $FORWARD_DECLARATIONS
576 // Step 2: method stubs.
577 $METHOD_STUBS
579 // Step 3: RegisterNatives.
581 static bool RegisterNativesImpl(JNIEnv* env) {
582 $REGISTER_NATIVES_IMPL
583 return true;
585 $CLOSE_NAMESPACE
586 #endif // ${HEADER_GUARD}
587 """)
588 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
589 base_index = script_components.index('base')
590 script_name = os.sep.join(script_components[base_index:])
591 values = {
592 'SCRIPT_NAME': script_name,
593 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
594 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
595 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(),
596 'METHOD_STUBS': self.GetMethodStubsString(),
597 'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
598 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString(),
599 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
600 'HEADER_GUARD': self.header_guard,
602 return WrapOutput(template.substitute(values))
604 def GetClassPathDefinitionsString(self):
605 ret = []
606 ret += [self.GetClassPathDefinitions()]
607 return '\n'.join(ret)
609 def GetForwardDeclarationsString(self):
610 ret = []
611 for native in self.natives:
612 if native.type != 'method':
613 ret += [self.GetForwardDeclaration(native)]
614 return '\n'.join(ret)
616 def GetMethodStubsString(self):
617 ret = []
618 for native in self.natives:
619 if native.type == 'method':
620 ret += [self.GetNativeMethodStub(native)]
621 for called_by_native in self.called_by_natives:
622 ret += [self.GetCalledByNativeMethodStub(called_by_native)]
623 return '\n'.join(ret)
625 def GetKMethodsString(self, clazz):
626 ret = []
627 for native in self.natives:
628 if (native.java_class_name == clazz or
629 (not native.java_class_name and clazz == self.class_name)):
630 ret += [self.GetKMethodArrayEntry(native)]
631 return '\n'.join(ret)
633 def GetRegisterNativesImplString(self):
634 """Returns the implementation for RegisterNatives."""
635 template = Template("""\
636 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
637 ${KMETHODS}
639 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
641 if (env->RegisterNatives(g_${JAVA_CLASS}_clazz,
642 kMethods${JAVA_CLASS},
643 kMethods${JAVA_CLASS}Size) < 0) {
644 LOG(ERROR) << "RegisterNatives failed in " << __FILE__;
645 return false;
647 """)
648 ret = [self.GetFindClasses()]
649 all_classes = self.GetUniqueClasses(self.natives)
650 all_classes[self.class_name] = self.fully_qualified_class
651 for clazz in all_classes:
652 kmethods = self.GetKMethodsString(clazz)
653 if kmethods:
654 values = {'JAVA_CLASS': clazz,
655 'KMETHODS': kmethods}
656 ret += [template.substitute(values)]
657 if not ret: return ''
658 return '\n' + '\n'.join(ret)
660 def GetOpenNamespaceString(self):
661 if self.namespace:
662 all_namespaces = ['namespace %s {' % ns
663 for ns in self.namespace.split('::')]
664 return '\n'.join(all_namespaces)
665 return ''
667 def GetCloseNamespaceString(self):
668 if self.namespace:
669 all_namespaces = ['} // namespace %s' % ns
670 for ns in self.namespace.split('::')]
671 all_namespaces.reverse()
672 return '\n'.join(all_namespaces) + '\n'
673 return ''
675 def GetJNIFirstParam(self, native):
676 ret = []
677 if native.type == 'method':
678 ret = ['jobject obj']
679 elif native.type == 'function':
680 if native.static:
681 ret = ['jclass clazz']
682 else:
683 ret = ['jobject obj']
684 return ret
686 def GetParamsInDeclaration(self, native):
687 """Returns the params for the stub declaration.
689 Args:
690 native: the native dictionary describing the method.
692 Returns:
693 A string containing the params.
695 return ',\n '.join(self.GetJNIFirstParam(native) +
696 [JavaDataTypeToC(param.datatype) + ' ' +
697 param.name
698 for param in native.params])
700 def GetCalledByNativeParamsInDeclaration(self, called_by_native):
701 return ',\n '.join([JavaDataTypeToC(param.datatype) + ' ' +
702 param.name
703 for param in called_by_native.params])
705 def GetForwardDeclaration(self, native):
706 template = Template("""
707 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
708 """)
709 values = {'RETURN': JavaDataTypeToC(native.return_type),
710 'NAME': native.name,
711 'PARAMS': self.GetParamsInDeclaration(native)}
712 return template.substitute(values)
714 def GetNativeMethodStub(self, native):
715 """Returns stubs for native methods."""
716 template = Template("""\
717 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {
718 DCHECK(${PARAM0_NAME}) << "${NAME}";
719 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
720 return native->${NAME}(env, obj${PARAMS_IN_CALL})${POST_CALL};
722 """)
723 params_for_call = ', '.join(p.name for p in native.params[1:])
724 if params_for_call:
725 params_for_call = ', ' + params_for_call
727 return_type = JavaDataTypeToC(native.return_type)
728 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
729 scoped_return_type = 'ScopedJavaLocalRef<' + return_type + '>'
730 post_call = '.Release()'
731 else:
732 scoped_return_type = return_type
733 post_call = ''
734 values = {
735 'RETURN': return_type,
736 'SCOPED_RETURN': scoped_return_type,
737 'NAME': native.name,
738 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native),
739 'PARAM0_NAME': native.params[0].name,
740 'P0_TYPE': native.p0_type,
741 'PARAMS_IN_CALL': params_for_call,
742 'POST_CALL': post_call
744 return template.substitute(values)
746 def GetCalledByNativeMethodStub(self, called_by_native):
747 """Returns a string."""
748 function_signature_template = Template("""\
749 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
750 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
751 function_header_template = Template("""\
752 ${FUNCTION_SIGNATURE} {""")
753 function_header_with_unused_template = Template("""\
754 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
755 ${FUNCTION_SIGNATURE} {""")
756 template = Template("""
757 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
758 ${FUNCTION_HEADER}
759 /* Must call RegisterNativesImpl() */
760 DCHECK(g_${JAVA_CLASS}_clazz);
761 jmethodID method_id =
762 ${GET_METHOD_ID_IMPL}
763 ${RETURN_DECLARATION}
764 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
765 method_id${PARAMS_IN_CALL})${POST_CALL};
766 ${CHECK_EXCEPTION}
767 ${RETURN_CLAUSE}
768 }""")
769 if called_by_native.static or called_by_native.is_constructor:
770 first_param_in_declaration = ''
771 first_param_in_call = ('g_%s_clazz' %
772 (called_by_native.java_class_name or
773 self.class_name))
774 else:
775 first_param_in_declaration = ', jobject obj'
776 first_param_in_call = 'obj'
777 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
778 called_by_native)
779 if params_in_declaration:
780 params_in_declaration = ', ' + params_in_declaration
781 params_for_call = ', '.join(param.name
782 for param in called_by_native.params)
783 if params_for_call:
784 params_for_call = ', ' + params_for_call
785 pre_call = ''
786 post_call = ''
787 if called_by_native.static_cast:
788 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
789 post_call = ')'
790 check_exception = ''
791 if not called_by_native.unchecked:
792 check_exception = 'base::android::CheckException(env);'
793 return_type = JavaDataTypeToC(called_by_native.return_type)
794 return_declaration = ''
795 return_clause = ''
796 if return_type != 'void':
797 pre_call = ' ' + pre_call
798 return_declaration = return_type + ' ret ='
799 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
800 return_type = 'ScopedJavaLocalRef<' + return_type + '>'
801 return_clause = 'return ' + return_type + '(env, ret);'
802 else:
803 return_clause = 'return ret;'
804 values = {
805 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
806 'METHOD': called_by_native.name,
807 'RETURN_TYPE': return_type,
808 'RETURN_DECLARATION': return_declaration,
809 'RETURN_CLAUSE': return_clause,
810 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
811 'PARAMS_IN_DECLARATION': params_in_declaration,
812 'STATIC': 'Static' if called_by_native.static else '',
813 'PRE_CALL': pre_call,
814 'POST_CALL': post_call,
815 'ENV_CALL': called_by_native.env_call,
816 'FIRST_PARAM_IN_CALL': first_param_in_call,
817 'PARAMS_IN_CALL': params_for_call,
818 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
819 'CHECK_EXCEPTION': check_exception,
820 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native)
822 values['FUNCTION_SIGNATURE'] = (
823 function_signature_template.substitute(values))
824 if called_by_native.system_class:
825 values['FUNCTION_HEADER'] = (
826 function_header_with_unused_template.substitute(values))
827 else:
828 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
829 return template.substitute(values)
831 def GetKMethodArrayEntry(self, native):
832 template = Template("""\
833 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""")
834 values = {'NAME': native.name,
835 'JNI_SIGNATURE': JniParams.Signature(native.params,
836 native.return_type,
837 True)}
838 return template.substitute(values)
840 def GetUniqueClasses(self, origin):
841 ret = {self.class_name: self.fully_qualified_class}
842 for entry in origin:
843 class_name = self.class_name
844 jni_class_path = self.fully_qualified_class
845 if entry.java_class_name:
846 class_name = entry.java_class_name
847 jni_class_path = self.fully_qualified_class + '$' + class_name
848 ret[class_name] = jni_class_path
849 return ret
851 def GetClassPathDefinitions(self):
852 """Returns the ClassPath constants."""
853 ret = []
854 template = Template("""\
855 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
856 native_classes = self.GetUniqueClasses(self.natives)
857 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
858 all_classes = native_classes
859 all_classes.update(called_by_native_classes)
860 for clazz in all_classes:
861 values = {
862 'JAVA_CLASS': clazz,
863 'JNI_CLASS_PATH': all_classes[clazz],
865 ret += [template.substitute(values)]
866 ret += ''
867 for clazz in called_by_native_classes:
868 template = Template("""\
869 // Leaking this jclass as we cannot use LazyInstance from some threads.
870 jclass g_${JAVA_CLASS}_clazz = NULL;""")
871 values = {
872 'JAVA_CLASS': clazz,
874 ret += [template.substitute(values)]
875 return '\n'.join(ret)
877 def GetFindClasses(self):
878 """Returns the imlementation of FindClass for all known classes."""
879 template = Template("""\
880 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
881 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
882 ret = []
883 for clazz in self.GetUniqueClasses(self.called_by_natives):
884 values = {'JAVA_CLASS': clazz}
885 ret += [template.substitute(values)]
886 return '\n'.join(ret)
888 def GetMethodIDImpl(self, called_by_native):
889 """Returns the implementation of GetMethodID."""
890 template = Template("""\
891 base::android::MethodID::LazyGet<
892 base::android::MethodID::TYPE_${STATIC}>(
893 env, g_${JAVA_CLASS}_clazz,
894 "${JNI_NAME}",
895 ${JNI_SIGNATURE},
896 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
897 """)
898 jni_name = called_by_native.name
899 jni_return_type = called_by_native.return_type
900 if called_by_native.is_constructor:
901 jni_name = '<init>'
902 jni_return_type = 'void'
903 values = {
904 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
905 'JNI_NAME': jni_name,
906 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
907 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
908 'JNI_SIGNATURE': JniParams.Signature(called_by_native.params,
909 jni_return_type,
910 True)
912 return template.substitute(values)
915 def WrapOutput(output):
916 ret = []
917 for line in output.splitlines():
918 # Do not wrap lines under 80 characters or preprocessor directives.
919 if len(line) < 80 or line.lstrip()[:1] == '#':
920 stripped = line.rstrip()
921 if len(ret) == 0 or len(ret[-1]) or len(stripped):
922 ret.append(stripped)
923 else:
924 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
925 subsequent_indent = first_line_indent + ' ' * 4
926 if line.startswith('//'):
927 subsequent_indent = '//' + subsequent_indent
928 wrapper = textwrap.TextWrapper(width=80,
929 subsequent_indent=subsequent_indent,
930 break_long_words=False)
931 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
932 ret += ['']
933 return '\n'.join(ret)
936 def ExtractJarInputFile(jar_file, input_file, out_dir):
937 """Extracts input file from jar and returns the filename.
939 The input file is extracted to the same directory that the generated jni
940 headers will be placed in. This is passed as an argument to script.
942 Args:
943 jar_file: the jar file containing the input files to extract.
944 input_files: the list of files to extract from the jar file.
945 out_dir: the name of the directories to extract to.
947 Returns:
948 the name of extracted input file.
950 jar_file = zipfile.ZipFile(jar_file)
952 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
953 try:
954 os.makedirs(out_dir)
955 except OSError as e:
956 if e.errno != errno.EEXIST:
957 raise
958 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
959 with open(extracted_file_name, 'w') as outfile:
960 outfile.write(jar_file.read(input_file))
962 return extracted_file_name
965 def GenerateJNIHeader(input_file, output_file, namespace, skip_if_same):
966 try:
967 if os.path.splitext(input_file)[1] == '.class':
968 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, namespace)
969 content = jni_from_javap.GetContent()
970 else:
971 jni_from_java_source = JNIFromJavaSource.CreateFromFile(input_file)
972 content = jni_from_java_source.GetContent()
973 except ParseError, e:
974 print e
975 sys.exit(1)
976 if output_file:
977 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
978 os.makedirs(os.path.dirname(os.path.abspath(output_file)))
979 if skip_if_same and os.path.exists(output_file):
980 with file(output_file, 'r') as f:
981 existing_content = f.read()
982 if existing_content == content:
983 return
984 with file(output_file, 'w') as f:
985 f.write(content)
986 else:
987 print output
990 def main(argv):
991 usage = """usage: %prog [OPTIONS]
992 This script will parse the given java source code extracting the native
993 declarations and print the header file to stdout (or a file).
994 See SampleForTests.java for more details.
996 option_parser = optparse.OptionParser(usage=usage)
997 option_parser.add_option('-j', dest='jar_file',
998 help='Extract the list of input files from'
999 ' a specified jar file.'
1000 ' Uses javap to extract the methods from a'
1001 ' pre-compiled class. --input should point'
1002 ' to pre-compiled Java .class files.')
1003 option_parser.add_option('-n', dest='namespace',
1004 help='Uses as a namespace in the generated header,'
1005 ' instead of the javap class name.')
1006 option_parser.add_option('--input_file',
1007 help='Single input file name. The output file name '
1008 'will be derived from it. Must be used with '
1009 '--output_dir.')
1010 option_parser.add_option('--output_dir',
1011 help='The output directory. Must be used with '
1012 '--input')
1013 option_parser.add_option('--optimize_generation', type="int",
1014 default=0, help='Whether we should optimize JNI '
1015 'generation by not regenerating files if they have '
1016 'not changed.')
1017 options, args = option_parser.parse_args(argv)
1018 if options.jar_file:
1019 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1020 options.output_dir)
1021 else:
1022 input_file = options.input_file
1023 output_file = None
1024 if options.output_dir:
1025 root_name = os.path.splitext(os.path.basename(input_file))[0]
1026 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
1027 GenerateJNIHeader(input_file, output_file, options.namespace,
1028 options.optimize_generation)
1031 if __name__ == '__main__':
1032 sys.exit(main(sys.argv))