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