Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / base / android / jni_generator / jni_generator.py
blob5d262c8933578a1ab588d0607f36f8ec8f03bad4
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
21 CHROMIUM_SRC = os.path.join(
22 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
23 BUILD_ANDROID_GYP = os.path.join(
24 CHROMIUM_SRC, 'build', 'android', 'gyp')
26 sys.path.append(BUILD_ANDROID_GYP)
28 from util import build_utils
31 class ParseError(Exception):
32 """Exception thrown when we can't parse the input file."""
34 def __init__(self, description, *context_lines):
35 Exception.__init__(self)
36 self.description = description
37 self.context_lines = context_lines
39 def __str__(self):
40 context = '\n'.join(self.context_lines)
41 return '***\nERROR: %s\n\n%s\n***' % (self.description, context)
44 class Param(object):
45 """Describes a param for a method, either java or native."""
47 def __init__(self, **kwargs):
48 self.datatype = kwargs['datatype']
49 self.name = kwargs['name']
52 class NativeMethod(object):
53 """Describes a C/C++ method that is called by Java code"""
55 def __init__(self, **kwargs):
56 self.static = kwargs['static']
57 self.java_class_name = kwargs['java_class_name']
58 self.return_type = kwargs['return_type']
59 self.name = kwargs['name']
60 self.params = kwargs['params']
61 if self.params:
62 assert type(self.params) is list
63 assert type(self.params[0]) is Param
64 if (self.params and
65 self.params[0].datatype == kwargs.get('ptr_type', 'int') and
66 self.params[0].name.startswith('native')):
67 self.type = 'method'
68 self.p0_type = self.params[0].name[len('native'):]
69 if kwargs.get('native_class_name'):
70 self.p0_type = kwargs['native_class_name']
71 else:
72 self.type = 'function'
73 self.method_id_var_name = kwargs.get('method_id_var_name', None)
76 class CalledByNative(object):
77 """Describes a java method exported to c/c++"""
79 def __init__(self, **kwargs):
80 self.system_class = kwargs['system_class']
81 self.unchecked = kwargs['unchecked']
82 self.static = kwargs['static']
83 self.java_class_name = kwargs['java_class_name']
84 self.return_type = kwargs['return_type']
85 self.name = kwargs['name']
86 self.params = kwargs['params']
87 self.method_id_var_name = kwargs.get('method_id_var_name', None)
88 self.signature = kwargs.get('signature')
89 self.is_constructor = kwargs.get('is_constructor', False)
90 self.env_call = GetEnvCall(self.is_constructor, self.static,
91 self.return_type)
92 self.static_cast = GetStaticCastForReturnType(self.return_type)
95 class ConstantField(object):
96 def __init__(self, **kwargs):
97 self.name = kwargs['name']
98 self.value = kwargs['value']
101 def JavaDataTypeToC(java_type):
102 """Returns a C datatype for the given java type."""
103 java_pod_type_map = {
104 'int': 'jint',
105 'byte': 'jbyte',
106 'char': 'jchar',
107 'short': 'jshort',
108 'boolean': 'jboolean',
109 'long': 'jlong',
110 'double': 'jdouble',
111 'float': 'jfloat',
113 java_type_map = {
114 'void': 'void',
115 'String': 'jstring',
116 'Throwable': 'jthrowable',
117 'java/lang/String': 'jstring',
118 'java/lang/Class': 'jclass',
119 'java/lang/Throwable': 'jthrowable',
122 if java_type in java_pod_type_map:
123 return java_pod_type_map[java_type]
124 elif java_type in java_type_map:
125 return java_type_map[java_type]
126 elif java_type.endswith('[]'):
127 if java_type[:-2] in java_pod_type_map:
128 return java_pod_type_map[java_type[:-2]] + 'Array'
129 return 'jobjectArray'
130 elif java_type.startswith('Class'):
131 # Checking just the start of the name, rather than a direct comparison,
132 # in order to handle generics.
133 return 'jclass'
134 else:
135 return 'jobject'
138 def WrapCTypeForDeclaration(c_type):
139 """Wrap the C datatype in a JavaRef if required."""
140 if re.match(RE_SCOPED_JNI_TYPES, c_type):
141 return 'const JavaParamRef<' + c_type + '>&'
142 else:
143 return c_type
146 def JavaDataTypeToCForDeclaration(java_type):
147 """Returns a JavaRef-wrapped C datatype for the given java type."""
148 return WrapCTypeForDeclaration(JavaDataTypeToC(java_type))
151 def JavaDataTypeToCForCalledByNativeParam(java_type):
152 """Returns a C datatype to be when calling from native."""
153 if java_type == 'int':
154 return 'JniIntWrapper'
155 else:
156 return JavaDataTypeToC(java_type)
159 def JavaReturnValueToC(java_type):
160 """Returns a valid C return value for the given java type."""
161 java_pod_type_map = {
162 'int': '0',
163 'byte': '0',
164 'char': '0',
165 'short': '0',
166 'boolean': 'false',
167 'long': '0',
168 'double': '0',
169 'float': '0',
170 'void': ''
172 return java_pod_type_map.get(java_type, 'NULL')
175 class JniParams(object):
176 _imports = []
177 _fully_qualified_class = ''
178 _package = ''
179 _inner_classes = []
180 _remappings = []
181 _implicit_imports = []
183 @staticmethod
184 def SetFullyQualifiedClass(fully_qualified_class):
185 JniParams._fully_qualified_class = 'L' + fully_qualified_class
186 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1])
188 @staticmethod
189 def AddAdditionalImport(class_name):
190 assert class_name.endswith('.class')
191 raw_class_name = class_name[:-len('.class')]
192 if '.' in raw_class_name:
193 raise SyntaxError('%s cannot be used in @JNIAdditionalImport. '
194 'Only import unqualified outer classes.' % class_name)
195 new_import = 'L%s/%s' % (JniParams._package, raw_class_name)
196 if new_import in JniParams._imports:
197 raise SyntaxError('Do not use JNIAdditionalImport on an already '
198 'imported class: %s' % (new_import.replace('/', '.')))
199 JniParams._imports += [new_import]
201 @staticmethod
202 def ExtractImportsAndInnerClasses(contents):
203 if not JniParams._package:
204 raise RuntimeError('SetFullyQualifiedClass must be called before '
205 'ExtractImportsAndInnerClasses')
206 contents = contents.replace('\n', '')
207 re_import = re.compile(r'import.*?(?P<class>\S*?);')
208 for match in re.finditer(re_import, contents):
209 JniParams._imports += ['L' + match.group('class').replace('.', '/')]
211 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W')
212 for match in re.finditer(re_inner, contents):
213 inner = match.group('name')
214 if not JniParams._fully_qualified_class.endswith(inner):
215 JniParams._inner_classes += [JniParams._fully_qualified_class + '$' +
216 inner]
218 re_additional_imports = re.compile(
219 r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
220 for match in re.finditer(re_additional_imports, contents):
221 for class_name in match.group('class_names').split(','):
222 JniParams.AddAdditionalImport(class_name.strip())
224 @staticmethod
225 def ParseJavaPSignature(signature_line):
226 prefix = 'Signature: '
227 index = signature_line.find(prefix)
228 if index == -1:
229 prefix = 'descriptor: '
230 index = signature_line.index(prefix)
231 return '"%s"' % signature_line[index + len(prefix):]
233 @staticmethod
234 def JavaToJni(param):
235 """Converts a java param into a JNI signature type."""
236 pod_param_map = {
237 'int': 'I',
238 'boolean': 'Z',
239 'char': 'C',
240 'short': 'S',
241 'long': 'J',
242 'double': 'D',
243 'float': 'F',
244 'byte': 'B',
245 'void': 'V',
247 object_param_list = [
248 'Ljava/lang/Boolean',
249 'Ljava/lang/Integer',
250 'Ljava/lang/Long',
251 'Ljava/lang/Object',
252 'Ljava/lang/String',
253 'Ljava/lang/Class',
254 'Ljava/lang/CharSequence',
255 'Ljava/lang/Runnable',
256 'Ljava/lang/Throwable',
259 prefix = ''
260 # Array?
261 while param[-2:] == '[]':
262 prefix += '['
263 param = param[:-2]
264 # Generic?
265 if '<' in param:
266 param = param[:param.index('<')]
267 if param in pod_param_map:
268 return prefix + pod_param_map[param]
269 if '/' in param:
270 # Coming from javap, use the fully qualified param directly.
271 return prefix + 'L' + JniParams.RemapClassName(param) + ';'
273 for qualified_name in (object_param_list +
274 [JniParams._fully_qualified_class] +
275 JniParams._inner_classes):
276 if (qualified_name.endswith('/' + param) or
277 qualified_name.endswith('$' + param.replace('.', '$')) or
278 qualified_name == 'L' + param):
279 return prefix + JniParams.RemapClassName(qualified_name) + ';'
281 # Is it from an import? (e.g. referecing Class from import pkg.Class;
282 # note that referencing an inner class Inner from import pkg.Class.Inner
283 # is not supported).
284 for qualified_name in JniParams._imports:
285 if qualified_name.endswith('/' + param):
286 # Ensure it's not an inner class.
287 components = qualified_name.split('/')
288 if len(components) > 2 and components[-2][0].isupper():
289 raise SyntaxError('Inner class (%s) can not be imported '
290 'and used by JNI (%s). Please import the outer '
291 'class and use Outer.Inner instead.' %
292 (qualified_name, param))
293 return prefix + JniParams.RemapClassName(qualified_name) + ';'
295 # Is it an inner class from an outer class import? (e.g. referencing
296 # Class.Inner from import pkg.Class).
297 if '.' in param:
298 components = param.split('.')
299 outer = '/'.join(components[:-1])
300 inner = components[-1]
301 for qualified_name in JniParams._imports:
302 if qualified_name.endswith('/' + outer):
303 return (prefix + JniParams.RemapClassName(qualified_name) +
304 '$' + inner + ';')
305 raise SyntaxError('Inner class (%s) can not be '
306 'used directly by JNI. Please import the outer '
307 'class, probably:\n'
308 'import %s.%s;' %
309 (param, JniParams._package.replace('/', '.'),
310 outer.replace('/', '.')))
312 JniParams._CheckImplicitImports(param)
314 # Type not found, falling back to same package as this class.
315 return (prefix + 'L' +
316 JniParams.RemapClassName(JniParams._package + '/' + param) + ';')
318 @staticmethod
319 def _CheckImplicitImports(param):
320 # Ensure implicit imports, such as java.lang.*, are not being treated
321 # as being in the same package.
322 if not JniParams._implicit_imports:
323 # This file was generated from android.jar and lists
324 # all classes that are implicitly imported.
325 with file(os.path.join(os.path.dirname(sys.argv[0]),
326 'android_jar.classes'), 'r') as f:
327 JniParams._implicit_imports = f.readlines()
328 for implicit_import in JniParams._implicit_imports:
329 implicit_import = implicit_import.strip().replace('.class', '')
330 implicit_import = implicit_import.replace('/', '.')
331 if implicit_import.endswith('.' + param):
332 raise SyntaxError('Ambiguous class (%s) can not be used directly '
333 'by JNI.\nPlease import it, probably:\n\n'
334 'import %s;' %
335 (param, implicit_import))
338 @staticmethod
339 def Signature(params, returns, wrap):
340 """Returns the JNI signature for the given datatypes."""
341 items = ['(']
342 items += [JniParams.JavaToJni(param.datatype) for param in params]
343 items += [')']
344 items += [JniParams.JavaToJni(returns)]
345 if wrap:
346 return '\n' + '\n'.join(['"' + item + '"' for item in items])
347 else:
348 return '"' + ''.join(items) + '"'
350 @staticmethod
351 def Parse(params):
352 """Parses the params into a list of Param objects."""
353 if not params:
354 return []
355 ret = []
356 for p in [p.strip() for p in params.split(',')]:
357 items = p.split(' ')
358 if 'final' in items:
359 items.remove('final')
360 param = Param(
361 datatype=items[0],
362 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
364 ret += [param]
365 return ret
367 @staticmethod
368 def RemapClassName(class_name):
369 """Remaps class names using the jarjar mapping table."""
370 for old, new in JniParams._remappings:
371 if old.endswith('**') and old[:-2] in class_name:
372 return class_name.replace(old[:-2], new, 1)
373 if '*' not in old and class_name.endswith(old):
374 return class_name.replace(old, new, 1)
376 return class_name
378 @staticmethod
379 def SetJarJarMappings(mappings):
380 """Parse jarjar mappings from a string."""
381 JniParams._remappings = []
382 for line in mappings.splitlines():
383 rule = line.split()
384 if rule[0] != 'rule':
385 continue
386 _, src, dest = rule
387 src = src.replace('.', '/')
388 dest = dest.replace('.', '/')
389 if src.endswith('**'):
390 src_real_name = src[:-2]
391 else:
392 assert not '*' in src
393 src_real_name = src
395 if dest.endswith('@0'):
396 JniParams._remappings.append((src, dest[:-2] + src_real_name))
397 elif dest.endswith('@1'):
398 assert '**' in src
399 JniParams._remappings.append((src, dest[:-2]))
400 else:
401 assert not '@' in dest
402 JniParams._remappings.append((src, dest))
405 def ExtractJNINamespace(contents):
406 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
407 m = re.findall(re_jni_namespace, contents)
408 if not m:
409 return ''
410 return m[0]
413 def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
414 re_package = re.compile('.*?package (.*?);')
415 matches = re.findall(re_package, contents)
416 if not matches:
417 raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
418 return (matches[0].replace('.', '/') + '/' +
419 os.path.splitext(os.path.basename(java_file_name))[0])
422 def ExtractNatives(contents, ptr_type):
423 """Returns a list of dict containing information about a native method."""
424 contents = contents.replace('\n', '')
425 natives = []
426 re_native = re.compile(r'(@NativeClassQualifiedName'
427 '\(\"(?P<native_class_name>.*?)\"\)\s+)?'
428 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
429 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
430 '(?P<return_type>\S*) '
431 '(?P<name>native\w+)\((?P<params>.*?)\);')
432 for match in re.finditer(re_native, contents):
433 native = NativeMethod(
434 static='static' in match.group('qualifiers'),
435 java_class_name=match.group('java_class_name'),
436 native_class_name=match.group('native_class_name'),
437 return_type=match.group('return_type'),
438 name=match.group('name').replace('native', ''),
439 params=JniParams.Parse(match.group('params')),
440 ptr_type=ptr_type)
441 natives += [native]
442 return natives
445 def GetStaticCastForReturnType(return_type):
446 type_map = { 'String' : 'jstring',
447 'java/lang/String' : 'jstring',
448 'Throwable': 'jthrowable',
449 'java/lang/Throwable': 'jthrowable',
450 'boolean[]': 'jbooleanArray',
451 'byte[]': 'jbyteArray',
452 'char[]': 'jcharArray',
453 'short[]': 'jshortArray',
454 'int[]': 'jintArray',
455 'long[]': 'jlongArray',
456 'float[]': 'jfloatArray',
457 'double[]': 'jdoubleArray' }
458 ret = type_map.get(return_type, None)
459 if ret:
460 return ret
461 if return_type.endswith('[]'):
462 return 'jobjectArray'
463 return None
466 def GetEnvCall(is_constructor, is_static, return_type):
467 """Maps the types availabe via env->Call__Method."""
468 if is_constructor:
469 return 'NewObject'
470 env_call_map = {'boolean': 'Boolean',
471 'byte': 'Byte',
472 'char': 'Char',
473 'short': 'Short',
474 'int': 'Int',
475 'long': 'Long',
476 'float': 'Float',
477 'void': 'Void',
478 'double': 'Double',
479 'Object': 'Object',
481 call = env_call_map.get(return_type, 'Object')
482 if is_static:
483 call = 'Static' + call
484 return 'Call' + call + 'Method'
487 def GetMangledParam(datatype):
488 """Returns a mangled identifier for the datatype."""
489 if len(datatype) <= 2:
490 return datatype.replace('[', 'A')
491 ret = ''
492 for i in range(1, len(datatype)):
493 c = datatype[i]
494 if c == '[':
495 ret += 'A'
496 elif c.isupper() or datatype[i - 1] in ['/', 'L']:
497 ret += c.upper()
498 return ret
501 def GetMangledMethodName(name, params, return_type):
502 """Returns a mangled method name for the given signature.
504 The returned name can be used as a C identifier and will be unique for all
505 valid overloads of the same method.
507 Args:
508 name: string.
509 params: list of Param.
510 return_type: string.
512 Returns:
513 A mangled name.
515 mangled_items = []
516 for datatype in [return_type] + [x.datatype for x in params]:
517 mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))]
518 mangled_name = name + '_'.join(mangled_items)
519 assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
520 return mangled_name
523 def MangleCalledByNatives(called_by_natives):
524 """Mangles all the overloads from the call_by_natives list."""
525 method_counts = collections.defaultdict(
526 lambda: collections.defaultdict(lambda: 0))
527 for called_by_native in called_by_natives:
528 java_class_name = called_by_native.java_class_name
529 name = called_by_native.name
530 method_counts[java_class_name][name] += 1
531 for called_by_native in called_by_natives:
532 java_class_name = called_by_native.java_class_name
533 method_name = called_by_native.name
534 method_id_var_name = method_name
535 if method_counts[java_class_name][method_name] > 1:
536 method_id_var_name = GetMangledMethodName(method_name,
537 called_by_native.params,
538 called_by_native.return_type)
539 called_by_native.method_id_var_name = method_id_var_name
540 return called_by_natives
543 # Regex to match the JNI types that should be wrapped in a JavaRef.
544 RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array')
547 # Regex to match a string like "@CalledByNative public void foo(int bar)".
548 RE_CALLED_BY_NATIVE = re.compile(
549 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
550 '\s+(?P<prefix>[\w ]*?)'
551 '\s*(?P<return_type>\S+?)'
552 '\s+(?P<name>\w+)'
553 '\s*\((?P<params>[^\)]*)\)')
556 def ExtractCalledByNatives(contents):
557 """Parses all methods annotated with @CalledByNative.
559 Args:
560 contents: the contents of the java file.
562 Returns:
563 A list of dict with information about the annotated methods.
564 TODO(bulach): return a CalledByNative object.
566 Raises:
567 ParseError: if unable to parse.
569 called_by_natives = []
570 for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
571 called_by_natives += [CalledByNative(
572 system_class=False,
573 unchecked='Unchecked' in match.group('Unchecked'),
574 static='static' in match.group('prefix'),
575 java_class_name=match.group('annotation') or '',
576 return_type=match.group('return_type'),
577 name=match.group('name'),
578 params=JniParams.Parse(match.group('params')))]
579 # Check for any @CalledByNative occurrences that weren't matched.
580 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
581 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
582 if '@CalledByNative' in line1:
583 raise ParseError('could not parse @CalledByNative method signature',
584 line1, line2)
585 return MangleCalledByNatives(called_by_natives)
588 class JNIFromJavaP(object):
589 """Uses 'javap' to parse a .class file and generate the JNI header file."""
591 def __init__(self, contents, options):
592 self.contents = contents
593 self.namespace = options.namespace
594 for line in contents:
595 class_name = re.match(
596 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
597 line)
598 if class_name:
599 self.fully_qualified_class = class_name.group('class_name')
600 break
601 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
602 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
603 # away the <...> and use the raw class name that Java 6 would've given us.
604 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
605 JniParams.SetFullyQualifiedClass(self.fully_qualified_class)
606 self.java_class_name = self.fully_qualified_class.split('/')[-1]
607 if not self.namespace:
608 self.namespace = 'JNI_' + self.java_class_name
609 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
610 '\((?P<params>.*?)\)')
611 self.called_by_natives = []
612 for lineno, content in enumerate(contents[2:], 2):
613 match = re.match(re_method, content)
614 if not match:
615 continue
616 self.called_by_natives += [CalledByNative(
617 system_class=True,
618 unchecked=False,
619 static='static' in match.group('prefix'),
620 java_class_name='',
621 return_type=match.group('return_type').replace('.', '/'),
622 name=match.group('name'),
623 params=JniParams.Parse(match.group('params').replace('.', '/')),
624 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
625 re_constructor = re.compile('(.*?)public ' +
626 self.fully_qualified_class.replace('/', '.') +
627 '\((?P<params>.*?)\)')
628 for lineno, content in enumerate(contents[2:], 2):
629 match = re.match(re_constructor, content)
630 if not match:
631 continue
632 self.called_by_natives += [CalledByNative(
633 system_class=True,
634 unchecked=False,
635 static=False,
636 java_class_name='',
637 return_type=self.fully_qualified_class,
638 name='Constructor',
639 params=JniParams.Parse(match.group('params').replace('.', '/')),
640 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
641 is_constructor=True)]
642 self.called_by_natives = MangleCalledByNatives(self.called_by_natives)
644 self.constant_fields = []
645 re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
646 re_constant_field_value = re.compile(
647 '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)')
648 for lineno, content in enumerate(contents[2:], 2):
649 match = re.match(re_constant_field, content)
650 if not match:
651 continue
652 value = re.match(re_constant_field_value, contents[lineno + 2])
653 if not value:
654 value = re.match(re_constant_field_value, contents[lineno + 3])
655 if value:
656 self.constant_fields.append(
657 ConstantField(name=match.group('name'),
658 value=value.group('value')))
660 self.inl_header_file_generator = InlHeaderFileGenerator(
661 self.namespace, self.fully_qualified_class, [],
662 self.called_by_natives, self.constant_fields, options)
664 def GetContent(self):
665 return self.inl_header_file_generator.GetContent()
667 @staticmethod
668 def CreateFromClass(class_file, options):
669 class_name = os.path.splitext(os.path.basename(class_file))[0]
670 p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
671 '-s', class_name],
672 cwd=os.path.dirname(class_file),
673 stdout=subprocess.PIPE,
674 stderr=subprocess.PIPE)
675 stdout, _ = p.communicate()
676 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
677 return jni_from_javap
680 class JNIFromJavaSource(object):
681 """Uses the given java source file to generate the JNI header file."""
683 # Match single line comments, multiline comments, character literals, and
684 # double-quoted strings.
685 _comment_remover_regex = re.compile(
686 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
687 re.DOTALL | re.MULTILINE)
689 def __init__(self, contents, fully_qualified_class, options):
690 contents = self._RemoveComments(contents)
691 JniParams.SetFullyQualifiedClass(fully_qualified_class)
692 JniParams.ExtractImportsAndInnerClasses(contents)
693 jni_namespace = ExtractJNINamespace(contents) or options.namespace
694 natives = ExtractNatives(contents, options.ptr_type)
695 called_by_natives = ExtractCalledByNatives(contents)
696 if len(natives) == 0 and len(called_by_natives) == 0:
697 raise SyntaxError('Unable to find any JNI methods for %s.' %
698 fully_qualified_class)
699 inl_header_file_generator = InlHeaderFileGenerator(
700 jni_namespace, fully_qualified_class, natives, called_by_natives,
701 [], options)
702 self.content = inl_header_file_generator.GetContent()
704 @classmethod
705 def _RemoveComments(cls, contents):
706 # We need to support both inline and block comments, and we need to handle
707 # strings that contain '//' or '/*'.
708 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
709 # parser. Maybe we could ditch JNIFromJavaSource and just always use
710 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
711 # http://code.google.com/p/chromium/issues/detail?id=138941
712 def replacer(match):
713 # Replace matches that are comments with nothing; return literals/strings
714 # unchanged.
715 s = match.group(0)
716 if s.startswith('/'):
717 return ''
718 else:
719 return s
720 return cls._comment_remover_regex.sub(replacer, contents)
722 def GetContent(self):
723 return self.content
725 @staticmethod
726 def CreateFromFile(java_file_name, options):
727 contents = file(java_file_name).read()
728 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
729 contents)
730 return JNIFromJavaSource(contents, fully_qualified_class, options)
733 class InlHeaderFileGenerator(object):
734 """Generates an inline header file for JNI integration."""
736 def __init__(self, namespace, fully_qualified_class, natives,
737 called_by_natives, constant_fields, options):
738 self.namespace = namespace
739 self.fully_qualified_class = fully_qualified_class
740 self.class_name = self.fully_qualified_class.split('/')[-1]
741 self.natives = natives
742 self.called_by_natives = called_by_natives
743 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
744 self.constant_fields = constant_fields
745 self.options = options
746 self.init_native = self.ExtractInitNative(options)
748 def ExtractInitNative(self, options):
749 for native in self.natives:
750 if options.jni_init_native_name == 'native' + native.name:
751 self.natives.remove(native)
752 return native
753 return None
755 def GetContent(self):
756 """Returns the content of the JNI binding file."""
757 template = Template("""\
758 // Copyright 2014 The Chromium Authors. All rights reserved.
759 // Use of this source code is governed by a BSD-style license that can be
760 // found in the LICENSE file.
763 // This file is autogenerated by
764 // ${SCRIPT_NAME}
765 // For
766 // ${FULLY_QUALIFIED_CLASS}
768 #ifndef ${HEADER_GUARD}
769 #define ${HEADER_GUARD}
771 #include <jni.h>
773 ${INCLUDES}
775 #include "base/android/jni_int_wrapper.h"
777 // Step 1: forward declarations.
778 namespace {
779 $CLASS_PATH_DEFINITIONS
780 $METHOD_ID_DEFINITIONS
781 } // namespace
783 $OPEN_NAMESPACE
785 $CONSTANT_FIELDS
787 // Step 2: method stubs.
788 $METHOD_STUBS
790 // Step 3: RegisterNatives.
791 $JNI_NATIVE_METHODS
792 $REGISTER_NATIVES
793 $CLOSE_NAMESPACE
794 $JNI_REGISTER_NATIVES
795 #endif // ${HEADER_GUARD}
796 """)
797 values = {
798 'SCRIPT_NAME': self.options.script_name,
799 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
800 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
801 'METHOD_ID_DEFINITIONS': self.GetMethodIDDefinitionsString(),
802 'CONSTANT_FIELDS': self.GetConstantFieldsString(),
803 'METHOD_STUBS': self.GetMethodStubsString(),
804 'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
805 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(),
806 'REGISTER_NATIVES': self.GetRegisterNativesString(),
807 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
808 'HEADER_GUARD': self.header_guard,
809 'INCLUDES': self.GetIncludesString(),
810 'JNI_REGISTER_NATIVES': self.GetJNIRegisterNativesString()
812 return WrapOutput(template.substitute(values))
814 def GetClassPathDefinitionsString(self):
815 ret = []
816 ret += [self.GetClassPathDefinitions()]
817 return '\n'.join(ret)
819 def GetMethodIDDefinitionsString(self):
820 """Returns the definition of method ids for the called by native methods."""
821 if not self.options.eager_called_by_natives:
822 return ''
823 template = Template("""\
824 jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = NULL;""")
825 ret = []
826 for called_by_native in self.called_by_natives:
827 values = {
828 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
829 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
831 ret += [template.substitute(values)]
832 return '\n'.join(ret)
834 def GetConstantFieldsString(self):
835 if not self.constant_fields:
836 return ''
837 ret = ['enum Java_%s_constant_fields {' % self.class_name]
838 for c in self.constant_fields:
839 ret += [' %s = %s,' % (c.name, c.value)]
840 ret += ['};']
841 return '\n'.join(ret)
843 def GetMethodStubsString(self):
844 """Returns the code corresponding to method stubs."""
845 ret = []
846 for native in self.natives:
847 ret += [self.GetNativeStub(native)]
848 if self.options.eager_called_by_natives:
849 ret += self.GetEagerCalledByNativeMethodStubs()
850 else:
851 ret += self.GetLazyCalledByNativeMethodStubs()
852 return '\n'.join(ret)
854 def GetLazyCalledByNativeMethodStubs(self):
855 return [self.GetLazyCalledByNativeMethodStub(called_by_native)
856 for called_by_native in self.called_by_natives]
858 def GetEagerCalledByNativeMethodStubs(self):
859 ret = []
860 if self.called_by_natives:
861 ret += ['namespace {']
862 for called_by_native in self.called_by_natives:
863 ret += [self.GetEagerCalledByNativeMethodStub(called_by_native)]
864 ret += ['} // namespace']
865 return ret
867 def GetIncludesString(self):
868 if not self.options.includes:
869 return ''
870 includes = self.options.includes.split(',')
871 return '\n'.join('#include "%s"' % x for x in includes)
873 def GetKMethodsString(self, clazz):
874 ret = []
875 for native in self.natives:
876 if (native.java_class_name == clazz or
877 (not native.java_class_name and clazz == self.class_name)):
878 ret += [self.GetKMethodArrayEntry(native)]
879 return '\n'.join(ret)
881 def SubstituteNativeMethods(self, template):
882 """Substitutes JAVA_CLASS and KMETHODS in the provided template."""
883 ret = []
884 all_classes = self.GetUniqueClasses(self.natives)
885 all_classes[self.class_name] = self.fully_qualified_class
886 for clazz in all_classes:
887 kmethods = self.GetKMethodsString(clazz)
888 if kmethods:
889 values = {'JAVA_CLASS': clazz,
890 'KMETHODS': kmethods}
891 ret += [template.substitute(values)]
892 if not ret: return ''
893 return '\n' + '\n'.join(ret)
895 def GetJNINativeMethodsString(self):
896 """Returns the implementation of the array of native methods."""
897 if self.options.native_exports and not self.options.native_exports_optional:
898 return ''
899 template = Template("""\
900 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
901 ${KMETHODS}
903 """)
904 return self.SubstituteNativeMethods(template)
906 def GetRegisterCalledByNativesImplString(self):
907 """Returns the code for registering the called by native methods."""
908 if not self.options.eager_called_by_natives:
909 return ''
910 template = Template("""\
911 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = ${GET_METHOD_ID_IMPL}
912 if (g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} == NULL) {
913 return false;
915 """)
916 ret = []
917 for called_by_native in self.called_by_natives:
918 values = {
919 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
920 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
921 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native),
923 ret += [template.substitute(values)]
924 return '\n'.join(ret)
926 def GetRegisterNativesString(self):
927 """Returns the code for RegisterNatives."""
928 template = Template("""\
929 ${REGISTER_NATIVES_SIGNATURE} {
930 ${EARLY_EXIT}
931 ${CLASSES}
932 ${NATIVES}
933 ${CALLED_BY_NATIVES}
934 return true;
936 """)
937 signature = 'static bool RegisterNativesImpl(JNIEnv* env'
938 if self.init_native:
939 signature += ', jclass clazz)'
940 else:
941 signature += ')'
943 early_exit = ''
944 if self.options.native_exports_optional:
945 early_exit = """\
946 if (base::android::IsManualJniRegistrationDisabled()) return true;
949 natives = self.GetRegisterNativesImplString()
950 called_by_natives = self.GetRegisterCalledByNativesImplString()
951 values = {'REGISTER_NATIVES_SIGNATURE': signature,
952 'EARLY_EXIT': early_exit,
953 'CLASSES': self.GetFindClasses(),
954 'NATIVES': natives,
955 'CALLED_BY_NATIVES': called_by_natives,
957 return template.substitute(values)
959 def GetRegisterNativesImplString(self):
960 """Returns the shared implementation for RegisterNatives."""
961 if self.options.native_exports and not self.options.native_exports_optional:
962 return ''
964 template = Template("""\
965 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
967 if (env->RegisterNatives(${JAVA_CLASS}_clazz(env),
968 kMethods${JAVA_CLASS},
969 kMethods${JAVA_CLASS}Size) < 0) {
970 jni_generator::HandleRegistrationError(
971 env, ${JAVA_CLASS}_clazz(env), __FILE__);
972 return false;
974 """)
975 return self.SubstituteNativeMethods(template)
977 def GetJNIRegisterNativesString(self):
978 """Returns the implementation for the JNI registration of native methods."""
979 if not self.init_native:
980 return ''
982 template = Template("""\
983 extern "C" JNIEXPORT bool JNICALL
984 Java_${FULLY_QUALIFIED_CLASS}_${INIT_NATIVE_NAME}(JNIEnv* env, jclass clazz) {
985 return ${NAMESPACE}RegisterNativesImpl(env, clazz);
987 """)
989 if self.options.native_exports:
990 java_name = JniParams.RemapClassName(self.fully_qualified_class)
991 java_name = java_name.replace('_', '_1').replace('/', '_')
992 else:
993 java_name = self.fully_qualified_class.replace('/', '_')
995 namespace = ''
996 if self.namespace:
997 namespace = self.namespace + '::'
998 values = {'FULLY_QUALIFIED_CLASS': java_name,
999 'INIT_NATIVE_NAME': 'native' + self.init_native.name,
1000 'NAMESPACE': namespace,
1001 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString()
1003 return template.substitute(values)
1005 def GetOpenNamespaceString(self):
1006 if self.namespace:
1007 all_namespaces = ['namespace %s {' % ns
1008 for ns in self.namespace.split('::')]
1009 return '\n'.join(all_namespaces)
1010 return ''
1012 def GetCloseNamespaceString(self):
1013 if self.namespace:
1014 all_namespaces = ['} // namespace %s' % ns
1015 for ns in self.namespace.split('::')]
1016 all_namespaces.reverse()
1017 return '\n'.join(all_namespaces) + '\n'
1018 return ''
1020 def GetJNIFirstParamType(self, native):
1021 if native.type == 'method':
1022 return 'jobject'
1023 elif native.type == 'function':
1024 if native.static:
1025 return 'jclass'
1026 else:
1027 return 'jobject'
1029 def GetJNIFirstParam(self, native, for_declaration):
1030 c_type = self.GetJNIFirstParamType(native)
1031 if for_declaration:
1032 c_type = WrapCTypeForDeclaration(c_type)
1033 return [c_type + ' jcaller']
1035 def GetParamsInDeclaration(self, native):
1036 """Returns the params for the forward declaration.
1038 Args:
1039 native: the native dictionary describing the method.
1041 Returns:
1042 A string containing the params.
1044 return ',\n '.join(self.GetJNIFirstParam(native, True) +
1045 [JavaDataTypeToCForDeclaration(param.datatype) + ' ' +
1046 param.name
1047 for param in native.params])
1049 def GetParamsInStub(self, native):
1050 """Returns the params for the stub declaration.
1052 Args:
1053 native: the native dictionary describing the method.
1055 Returns:
1056 A string containing the params.
1058 return ',\n '.join(self.GetJNIFirstParam(native, False) +
1059 [JavaDataTypeToC(param.datatype) + ' ' +
1060 param.name
1061 for param in native.params])
1063 def GetCalledByNativeParamsInDeclaration(self, called_by_native):
1064 return ',\n '.join([
1065 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
1066 param.name
1067 for param in called_by_native.params])
1069 def GetStubName(self, native):
1070 """Return the name of the stub function for this native method.
1072 Args:
1073 native: the native dictionary describing the method.
1075 Returns:
1076 A string with the stub function name (used by the JVM).
1078 template = Template("Java_${JAVA_NAME}_native${NAME}")
1080 java_name = JniParams.RemapClassName(self.fully_qualified_class)
1081 java_name = java_name.replace('_', '_1').replace('/', '_')
1082 if native.java_class_name:
1083 java_name += '_00024' + native.java_class_name
1085 values = {'NAME': native.name,
1086 'JAVA_NAME': java_name}
1087 return template.substitute(values)
1089 def GetJavaParamRefForCall(self, c_type, name):
1090 return Template('JavaParamRef<${TYPE}>(env, ${NAME})').substitute({
1091 'TYPE': c_type,
1092 'NAME': name,
1095 def GetJNIFirstParamForCall(self, native):
1096 c_type = self.GetJNIFirstParamType(native)
1097 return [self.GetJavaParamRefForCall(c_type, 'jcaller')]
1099 def GetNativeStub(self, native):
1100 is_method = native.type == 'method'
1102 if is_method:
1103 params = native.params[1:]
1104 else:
1105 params = native.params
1106 params_in_call = []
1107 if not self.options.pure_native_methods:
1108 params_in_call = ['env'] + self.GetJNIFirstParamForCall(native)
1109 for p in params:
1110 c_type = JavaDataTypeToC(p.datatype)
1111 if re.match(RE_SCOPED_JNI_TYPES, c_type):
1112 params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name))
1113 else:
1114 params_in_call.append(p.name)
1115 params_in_call = ', '.join(params_in_call)
1117 if self.options.native_exports:
1118 stub_visibility = 'extern "C" __attribute__((visibility("default")))\n'
1119 else:
1120 stub_visibility = 'static '
1121 return_type = return_declaration = JavaDataTypeToC(native.return_type)
1122 post_call = ''
1123 if re.match(RE_SCOPED_JNI_TYPES, return_type):
1124 post_call = '.Release()'
1125 return_declaration = 'ScopedJavaLocalRef<' + return_type + '>'
1126 values = {
1127 'RETURN': return_type,
1128 'RETURN_DECLARATION': return_declaration,
1129 'NAME': native.name,
1130 'PARAMS': self.GetParamsInDeclaration(native),
1131 'PARAMS_IN_STUB': self.GetParamsInStub(native),
1132 'PARAMS_IN_CALL': params_in_call,
1133 'POST_CALL': post_call,
1134 'STUB_NAME': self.GetStubName(native),
1135 'STUB_VISIBILITY': stub_visibility,
1138 if is_method:
1139 optional_error_return = JavaReturnValueToC(native.return_type)
1140 if optional_error_return:
1141 optional_error_return = ', ' + optional_error_return
1142 values.update({
1143 'OPTIONAL_ERROR_RETURN': optional_error_return,
1144 'PARAM0_NAME': native.params[0].name,
1145 'P0_TYPE': native.p0_type,
1147 template = Template("""\
1148 ${STUB_VISIBILITY}${RETURN} ${STUB_NAME}(JNIEnv* env,
1149 ${PARAMS_IN_STUB}) {
1150 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
1151 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
1152 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
1154 """)
1155 else:
1156 template = Template("""
1157 static ${RETURN_DECLARATION} ${NAME}(JNIEnv* env, ${PARAMS});
1159 ${STUB_VISIBILITY}${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
1160 return ${NAME}(${PARAMS_IN_CALL})${POST_CALL};
1162 """)
1164 return template.substitute(values)
1166 def GetArgument(self, param):
1167 return ('as_jint(' + param.name + ')'
1168 if param.datatype == 'int' else param.name)
1170 def GetArgumentsInCall(self, params):
1171 """Return a string of arguments to call from native into Java"""
1172 return [self.GetArgument(p) for p in params]
1174 def GetCalledByNativeValues(self, called_by_native):
1175 """Fills in necessary values for the CalledByNative methods."""
1176 java_class = called_by_native.java_class_name or self.class_name
1177 if called_by_native.static or called_by_native.is_constructor:
1178 first_param_in_declaration = ''
1179 first_param_in_call = ('%s_clazz(env)' % java_class)
1180 else:
1181 first_param_in_declaration = ', jobject obj'
1182 first_param_in_call = 'obj'
1183 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
1184 called_by_native)
1185 if params_in_declaration:
1186 params_in_declaration = ', ' + params_in_declaration
1187 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
1188 if params_in_call:
1189 params_in_call = ', ' + params_in_call
1190 pre_call = ''
1191 post_call = ''
1192 if called_by_native.static_cast:
1193 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
1194 post_call = ')'
1195 check_exception = ''
1196 if not called_by_native.unchecked:
1197 check_exception = 'jni_generator::CheckException(env);'
1198 return_type = JavaDataTypeToC(called_by_native.return_type)
1199 optional_error_return = JavaReturnValueToC(called_by_native.return_type)
1200 if optional_error_return:
1201 optional_error_return = ', ' + optional_error_return
1202 return_declaration = ''
1203 return_clause = ''
1204 if return_type != 'void':
1205 pre_call = ' ' + pre_call
1206 return_declaration = return_type + ' ret ='
1207 if re.match(RE_SCOPED_JNI_TYPES, return_type):
1208 return_type = 'ScopedJavaLocalRef<' + return_type + '>'
1209 return_clause = 'return ' + return_type + '(env, ret);'
1210 else:
1211 return_clause = 'return ret;'
1212 return {
1213 'JAVA_CLASS': java_class,
1214 'RETURN_TYPE': return_type,
1215 'OPTIONAL_ERROR_RETURN': optional_error_return,
1216 'RETURN_DECLARATION': return_declaration,
1217 'RETURN_CLAUSE': return_clause,
1218 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
1219 'PARAMS_IN_DECLARATION': params_in_declaration,
1220 'PRE_CALL': pre_call,
1221 'POST_CALL': post_call,
1222 'ENV_CALL': called_by_native.env_call,
1223 'FIRST_PARAM_IN_CALL': first_param_in_call,
1224 'PARAMS_IN_CALL': params_in_call,
1225 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1226 'CHECK_EXCEPTION': check_exception,
1227 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native)
1230 def GetEagerCalledByNativeMethodStub(self, called_by_native):
1231 """Returns the implementation of the called by native method."""
1232 template = Template("""
1233 static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\
1234 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION}) {
1235 ${RETURN_DECLARATION}${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1236 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL};
1237 ${RETURN_CLAUSE}
1238 }""")
1239 values = self.GetCalledByNativeValues(called_by_native)
1240 return template.substitute(values)
1242 def GetLazyCalledByNativeMethodStub(self, called_by_native):
1243 """Returns a string."""
1244 function_signature_template = Template("""\
1245 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
1246 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
1247 function_header_template = Template("""\
1248 ${FUNCTION_SIGNATURE} {""")
1249 function_header_with_unused_template = Template("""\
1250 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
1251 ${FUNCTION_SIGNATURE} {""")
1252 template = Template("""
1253 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
1254 ${FUNCTION_HEADER}
1255 /* Must call RegisterNativesImpl() */
1256 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
1257 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN});
1258 jmethodID method_id =
1259 ${GET_METHOD_ID_IMPL}
1260 ${RETURN_DECLARATION}
1261 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1262 method_id${PARAMS_IN_CALL})${POST_CALL};
1263 ${CHECK_EXCEPTION}
1264 ${RETURN_CLAUSE}
1265 }""")
1266 values = self.GetCalledByNativeValues(called_by_native)
1267 values['FUNCTION_SIGNATURE'] = (
1268 function_signature_template.substitute(values))
1269 if called_by_native.system_class:
1270 values['FUNCTION_HEADER'] = (
1271 function_header_with_unused_template.substitute(values))
1272 else:
1273 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
1274 return template.substitute(values)
1276 def GetKMethodArrayEntry(self, native):
1277 template = Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' +
1278 'reinterpret_cast<void*>(${STUB_NAME}) },')
1279 values = {'NAME': native.name,
1280 'JNI_SIGNATURE': JniParams.Signature(native.params,
1281 native.return_type,
1282 True),
1283 'STUB_NAME': self.GetStubName(native)}
1284 return template.substitute(values)
1286 def GetUniqueClasses(self, origin):
1287 ret = {self.class_name: self.fully_qualified_class}
1288 for entry in origin:
1289 class_name = self.class_name
1290 jni_class_path = self.fully_qualified_class
1291 if entry.java_class_name:
1292 class_name = entry.java_class_name
1293 jni_class_path = self.fully_qualified_class + '$' + class_name
1294 ret[class_name] = jni_class_path
1295 return ret
1297 def GetClassPathDefinitions(self):
1298 """Returns the ClassPath constants."""
1299 ret = []
1300 template = Template("""\
1301 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
1302 native_classes = self.GetUniqueClasses(self.natives)
1303 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
1304 if self.options.native_exports:
1305 all_classes = called_by_native_classes
1306 else:
1307 all_classes = native_classes
1308 all_classes.update(called_by_native_classes)
1310 for clazz in all_classes:
1311 values = {
1312 'JAVA_CLASS': clazz,
1313 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]),
1315 ret += [template.substitute(values)]
1316 ret += ''
1318 class_getter_methods = []
1319 if self.options.native_exports:
1320 template = Template("""\
1321 // Leaking this jclass as we cannot use LazyInstance from some threads.
1322 base::subtle::AtomicWord g_${JAVA_CLASS}_clazz __attribute__((unused)) = 0;
1323 #define ${JAVA_CLASS}_clazz(env) \
1324 base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \
1325 &g_${JAVA_CLASS}_clazz)""")
1326 else:
1327 template = Template("""\
1328 // Leaking this jclass as we cannot use LazyInstance from some threads.
1329 jclass g_${JAVA_CLASS}_clazz = NULL;
1330 #define ${JAVA_CLASS}_clazz(env) g_${JAVA_CLASS}_clazz""")
1332 for clazz in called_by_native_classes:
1333 values = {
1334 'JAVA_CLASS': clazz,
1336 ret += [template.substitute(values)]
1338 return '\n'.join(ret)
1340 def GetFindClasses(self):
1341 """Returns the imlementation of FindClass for all known classes."""
1342 if self.init_native:
1343 if self.options.native_exports:
1344 template = Template("""\
1345 base::subtle::Release_Store(&g_${JAVA_CLASS}_clazz,
1346 static_cast<base::subtle::AtomicWord>(env->NewWeakGlobalRef(clazz));""")
1347 else:
1348 template = Template("""\
1349 g_${JAVA_CLASS}_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));""")
1350 else:
1351 if self.options.native_exports:
1352 return '\n'
1353 template = Template("""\
1354 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
1355 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
1356 ret = []
1357 for clazz in self.GetUniqueClasses(self.called_by_natives):
1358 values = {'JAVA_CLASS': clazz}
1359 ret += [template.substitute(values)]
1360 return '\n'.join(ret)
1362 def GetMethodIDImpl(self, called_by_native):
1363 """Returns the implementation of GetMethodID."""
1364 if self.options.eager_called_by_natives:
1365 template = Template("""\
1366 env->Get${STATIC_METHOD_PART}MethodID(
1367 ${JAVA_CLASS}_clazz(env),
1368 "${JNI_NAME}", ${JNI_SIGNATURE});""")
1369 else:
1370 template = Template("""\
1371 base::android::MethodID::LazyGet<
1372 base::android::MethodID::TYPE_${STATIC}>(
1373 env, ${JAVA_CLASS}_clazz(env),
1374 "${JNI_NAME}",
1375 ${JNI_SIGNATURE},
1376 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
1377 """)
1378 jni_name = called_by_native.name
1379 jni_return_type = called_by_native.return_type
1380 if called_by_native.is_constructor:
1381 jni_name = '<init>'
1382 jni_return_type = 'void'
1383 if called_by_native.signature:
1384 signature = called_by_native.signature
1385 else:
1386 signature = JniParams.Signature(called_by_native.params,
1387 jni_return_type,
1388 True)
1389 values = {
1390 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
1391 'JNI_NAME': jni_name,
1392 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1393 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
1394 'STATIC_METHOD_PART': 'Static' if called_by_native.static else '',
1395 'JNI_SIGNATURE': signature,
1397 return template.substitute(values)
1400 def WrapOutput(output):
1401 ret = []
1402 for line in output.splitlines():
1403 # Do not wrap lines under 80 characters or preprocessor directives.
1404 if len(line) < 80 or line.lstrip()[:1] == '#':
1405 stripped = line.rstrip()
1406 if len(ret) == 0 or len(ret[-1]) or len(stripped):
1407 ret.append(stripped)
1408 else:
1409 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
1410 subsequent_indent = first_line_indent + ' ' * 4
1411 if line.startswith('//'):
1412 subsequent_indent = '//' + subsequent_indent
1413 wrapper = textwrap.TextWrapper(width=80,
1414 subsequent_indent=subsequent_indent,
1415 break_long_words=False)
1416 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
1417 ret += ['']
1418 return '\n'.join(ret)
1421 def ExtractJarInputFile(jar_file, input_file, out_dir):
1422 """Extracts input file from jar and returns the filename.
1424 The input file is extracted to the same directory that the generated jni
1425 headers will be placed in. This is passed as an argument to script.
1427 Args:
1428 jar_file: the jar file containing the input files to extract.
1429 input_files: the list of files to extract from the jar file.
1430 out_dir: the name of the directories to extract to.
1432 Returns:
1433 the name of extracted input file.
1435 jar_file = zipfile.ZipFile(jar_file)
1437 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
1438 try:
1439 os.makedirs(out_dir)
1440 except OSError as e:
1441 if e.errno != errno.EEXIST:
1442 raise
1443 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
1444 with open(extracted_file_name, 'w') as outfile:
1445 outfile.write(jar_file.read(input_file))
1447 return extracted_file_name
1450 def GenerateJNIHeader(input_file, output_file, options):
1451 try:
1452 if os.path.splitext(input_file)[1] == '.class':
1453 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
1454 content = jni_from_javap.GetContent()
1455 else:
1456 jni_from_java_source = JNIFromJavaSource.CreateFromFile(
1457 input_file, options)
1458 content = jni_from_java_source.GetContent()
1459 except ParseError, e:
1460 print e
1461 sys.exit(1)
1462 if output_file:
1463 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
1464 os.makedirs(os.path.dirname(os.path.abspath(output_file)))
1465 if options.optimize_generation and os.path.exists(output_file):
1466 with file(output_file, 'r') as f:
1467 existing_content = f.read()
1468 if existing_content == content:
1469 return
1470 with file(output_file, 'w') as f:
1471 f.write(content)
1472 else:
1473 print content
1476 def GetScriptName():
1477 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
1478 base_index = 0
1479 for idx, value in enumerate(script_components):
1480 if value == 'base' or value == 'third_party':
1481 base_index = idx
1482 break
1483 return os.sep.join(script_components[base_index:])
1486 def main(argv):
1487 usage = """usage: %prog [OPTIONS]
1488 This script will parse the given java source code extracting the native
1489 declarations and print the header file to stdout (or a file).
1490 See SampleForTests.java for more details.
1492 option_parser = optparse.OptionParser(usage=usage)
1493 build_utils.AddDepfileOption(option_parser)
1495 option_parser.add_option('-j', '--jar_file', dest='jar_file',
1496 help='Extract the list of input files from'
1497 ' a specified jar file.'
1498 ' Uses javap to extract the methods from a'
1499 ' pre-compiled class. --input should point'
1500 ' to pre-compiled Java .class files.')
1501 option_parser.add_option('-n', dest='namespace',
1502 help='Uses as a namespace in the generated header '
1503 'instead of the javap class name, or when there is '
1504 'no JNINamespace annotation in the java source.')
1505 option_parser.add_option('--input_file',
1506 help='Single input file name. The output file name '
1507 'will be derived from it. Must be used with '
1508 '--output_dir.')
1509 option_parser.add_option('--output_dir',
1510 help='The output directory. Must be used with '
1511 '--input')
1512 option_parser.add_option('--optimize_generation', type="int",
1513 default=0, help='Whether we should optimize JNI '
1514 'generation by not regenerating files if they have '
1515 'not changed.')
1516 option_parser.add_option('--jarjar',
1517 help='Path to optional jarjar rules file.')
1518 option_parser.add_option('--script_name', default=GetScriptName(),
1519 help='The name of this script in the generated '
1520 'header.')
1521 option_parser.add_option('--includes',
1522 help='The comma-separated list of header files to '
1523 'include in the generated header.')
1524 option_parser.add_option('--pure_native_methods',
1525 action='store_true', dest='pure_native_methods',
1526 help='When true, the native methods will be called '
1527 'without any JNI-specific arguments.')
1528 option_parser.add_option('--ptr_type', default='int',
1529 type='choice', choices=['int', 'long'],
1530 help='The type used to represent native pointers in '
1531 'Java code. For 32-bit, use int; '
1532 'for 64-bit, use long.')
1533 option_parser.add_option('--jni_init_native_name', default='',
1534 help='The name of the JNI registration method that '
1535 'is used to initialize all native methods. If a '
1536 'method with this name is not present in the Java '
1537 'source file, setting this option is a no-op. When '
1538 'a method with this name is found however, the '
1539 'naming convention Java_<packageName>_<className> '
1540 'will limit the initialization to only the '
1541 'top-level class.')
1542 option_parser.add_option('--eager_called_by_natives',
1543 action='store_true', dest='eager_called_by_natives',
1544 help='When true, the called-by-native methods will '
1545 'be initialized in a non-atomic way.')
1546 option_parser.add_option('--cpp', default='cpp',
1547 help='The path to cpp command.')
1548 option_parser.add_option('--javap', default='javap',
1549 help='The path to javap command.')
1550 option_parser.add_option('--native_exports', action='store_true',
1551 help='Native method registration through .so '
1552 'exports.')
1553 option_parser.add_option('--native_exports_optional', action='store_true',
1554 help='Support both explicit and native method'
1555 'registration.')
1556 options, args = option_parser.parse_args(argv)
1557 if options.native_exports_optional:
1558 options.native_exports = True
1559 if options.jar_file:
1560 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1561 options.output_dir)
1562 elif options.input_file:
1563 input_file = options.input_file
1564 else:
1565 option_parser.print_help()
1566 print '\nError: Must specify --jar_file or --input_file.'
1567 return 1
1568 output_file = None
1569 if options.output_dir:
1570 root_name = os.path.splitext(os.path.basename(input_file))[0]
1571 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
1572 if options.jarjar:
1573 with open(options.jarjar) as f:
1574 JniParams.SetJarJarMappings(f.read())
1575 GenerateJNIHeader(input_file, output_file, options)
1577 if options.depfile:
1578 build_utils.WriteDepfile(
1579 options.depfile,
1580 build_utils.GetPythonDependencies())
1583 if __name__ == '__main__':
1584 sys.exit(main(sys.argv))