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