Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / base / android / jni_generator / jni_generator.py
blob74d0117495dd9362a280c7a8d706454fca02fa59
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 'java/lang/String': 'jstring',
117 'java/lang/Class': 'jclass',
120 if java_type in java_pod_type_map:
121 return java_pod_type_map[java_type]
122 elif java_type in java_type_map:
123 return java_type_map[java_type]
124 elif java_type.endswith('[]'):
125 if java_type[:-2] in java_pod_type_map:
126 return java_pod_type_map[java_type[:-2]] + 'Array'
127 return 'jobjectArray'
128 elif java_type.startswith('Class'):
129 # Checking just the start of the name, rather than a direct comparison,
130 # in order to handle generics.
131 return 'jclass'
132 else:
133 return 'jobject'
136 def JavaDataTypeToCForCalledByNativeParam(java_type):
137 """Returns a C datatype to be when calling from native."""
138 if java_type == 'int':
139 return 'JniIntWrapper'
140 else:
141 return JavaDataTypeToC(java_type)
144 def JavaReturnValueToC(java_type):
145 """Returns a valid C return value for the given java type."""
146 java_pod_type_map = {
147 'int': '0',
148 'byte': '0',
149 'char': '0',
150 'short': '0',
151 'boolean': 'false',
152 'long': '0',
153 'double': '0',
154 'float': '0',
155 'void': ''
157 return java_pod_type_map.get(java_type, 'NULL')
160 class JniParams(object):
161 _imports = []
162 _fully_qualified_class = ''
163 _package = ''
164 _inner_classes = []
165 _remappings = []
166 _implicit_imports = []
168 @staticmethod
169 def SetFullyQualifiedClass(fully_qualified_class):
170 JniParams._fully_qualified_class = 'L' + fully_qualified_class
171 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1])
173 @staticmethod
174 def AddAdditionalImport(class_name):
175 assert class_name.endswith('.class')
176 raw_class_name = class_name[:-len('.class')]
177 if '.' in raw_class_name:
178 raise SyntaxError('%s cannot be used in @JNIAdditionalImport. '
179 'Only import unqualified outer classes.' % class_name)
180 new_import = 'L%s/%s' % (JniParams._package, raw_class_name)
181 if new_import in JniParams._imports:
182 raise SyntaxError('Do not use JNIAdditionalImport on an already '
183 'imported class: %s' % (new_import.replace('/', '.')))
184 JniParams._imports += [new_import]
186 @staticmethod
187 def ExtractImportsAndInnerClasses(contents):
188 if not JniParams._package:
189 raise RuntimeError('SetFullyQualifiedClass must be called before '
190 'ExtractImportsAndInnerClasses')
191 contents = contents.replace('\n', '')
192 re_import = re.compile(r'import.*?(?P<class>\S*?);')
193 for match in re.finditer(re_import, contents):
194 JniParams._imports += ['L' + match.group('class').replace('.', '/')]
196 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W')
197 for match in re.finditer(re_inner, contents):
198 inner = match.group('name')
199 if not JniParams._fully_qualified_class.endswith(inner):
200 JniParams._inner_classes += [JniParams._fully_qualified_class + '$' +
201 inner]
203 re_additional_imports = re.compile(
204 r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
205 for match in re.finditer(re_additional_imports, contents):
206 for class_name in match.group('class_names').split(','):
207 JniParams.AddAdditionalImport(class_name.strip())
209 @staticmethod
210 def ParseJavaPSignature(signature_line):
211 prefix = 'Signature: '
212 index = signature_line.find(prefix)
213 if index == -1:
214 prefix = 'descriptor: '
215 index = signature_line.index(prefix)
216 return '"%s"' % signature_line[index + len(prefix):]
218 @staticmethod
219 def JavaToJni(param):
220 """Converts a java param into a JNI signature type."""
221 pod_param_map = {
222 'int': 'I',
223 'boolean': 'Z',
224 'char': 'C',
225 'short': 'S',
226 'long': 'J',
227 'double': 'D',
228 'float': 'F',
229 'byte': 'B',
230 'void': 'V',
232 object_param_list = [
233 'Ljava/lang/Boolean',
234 'Ljava/lang/Integer',
235 'Ljava/lang/Long',
236 'Ljava/lang/Object',
237 'Ljava/lang/String',
238 'Ljava/lang/Class',
239 'Ljava/lang/CharSequence',
240 'Ljava/lang/Runnable',
241 'Ljava/lang/Throwable',
244 prefix = ''
245 # Array?
246 while param[-2:] == '[]':
247 prefix += '['
248 param = param[:-2]
249 # Generic?
250 if '<' in param:
251 param = param[:param.index('<')]
252 if param in pod_param_map:
253 return prefix + pod_param_map[param]
254 if '/' in param:
255 # Coming from javap, use the fully qualified param directly.
256 return prefix + 'L' + JniParams.RemapClassName(param) + ';'
258 for qualified_name in (object_param_list +
259 [JniParams._fully_qualified_class] +
260 JniParams._inner_classes):
261 if (qualified_name.endswith('/' + param) or
262 qualified_name.endswith('$' + param.replace('.', '$')) or
263 qualified_name == 'L' + param):
264 return prefix + JniParams.RemapClassName(qualified_name) + ';'
266 # Is it from an import? (e.g. referecing Class from import pkg.Class;
267 # note that referencing an inner class Inner from import pkg.Class.Inner
268 # is not supported).
269 for qualified_name in JniParams._imports:
270 if qualified_name.endswith('/' + param):
271 # Ensure it's not an inner class.
272 components = qualified_name.split('/')
273 if len(components) > 2 and components[-2][0].isupper():
274 raise SyntaxError('Inner class (%s) can not be imported '
275 'and used by JNI (%s). Please import the outer '
276 'class and use Outer.Inner instead.' %
277 (qualified_name, param))
278 return prefix + JniParams.RemapClassName(qualified_name) + ';'
280 # Is it an inner class from an outer class import? (e.g. referencing
281 # Class.Inner from import pkg.Class).
282 if '.' in param:
283 components = param.split('.')
284 outer = '/'.join(components[:-1])
285 inner = components[-1]
286 for qualified_name in JniParams._imports:
287 if qualified_name.endswith('/' + outer):
288 return (prefix + JniParams.RemapClassName(qualified_name) +
289 '$' + inner + ';')
290 raise SyntaxError('Inner class (%s) can not be '
291 'used directly by JNI. Please import the outer '
292 'class, probably:\n'
293 'import %s.%s;' %
294 (param, JniParams._package.replace('/', '.'),
295 outer.replace('/', '.')))
297 JniParams._CheckImplicitImports(param)
299 # Type not found, falling back to same package as this class.
300 return (prefix + 'L' +
301 JniParams.RemapClassName(JniParams._package + '/' + param) + ';')
303 @staticmethod
304 def _CheckImplicitImports(param):
305 # Ensure implicit imports, such as java.lang.*, are not being treated
306 # as being in the same package.
307 if not JniParams._implicit_imports:
308 # This file was generated from android.jar and lists
309 # all classes that are implicitly imported.
310 with file(os.path.join(os.path.dirname(sys.argv[0]),
311 'android_jar.classes'), 'r') as f:
312 JniParams._implicit_imports = f.readlines()
313 for implicit_import in JniParams._implicit_imports:
314 implicit_import = implicit_import.strip().replace('.class', '')
315 implicit_import = implicit_import.replace('/', '.')
316 if implicit_import.endswith('.' + param):
317 raise SyntaxError('Ambiguous class (%s) can not be used directly '
318 'by JNI.\nPlease import it, probably:\n\n'
319 'import %s;' %
320 (param, implicit_import))
323 @staticmethod
324 def Signature(params, returns, wrap):
325 """Returns the JNI signature for the given datatypes."""
326 items = ['(']
327 items += [JniParams.JavaToJni(param.datatype) for param in params]
328 items += [')']
329 items += [JniParams.JavaToJni(returns)]
330 if wrap:
331 return '\n' + '\n'.join(['"' + item + '"' for item in items])
332 else:
333 return '"' + ''.join(items) + '"'
335 @staticmethod
336 def Parse(params):
337 """Parses the params into a list of Param objects."""
338 if not params:
339 return []
340 ret = []
341 for p in [p.strip() for p in params.split(',')]:
342 items = p.split(' ')
343 if 'final' in items:
344 items.remove('final')
345 param = Param(
346 datatype=items[0],
347 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
349 ret += [param]
350 return ret
352 @staticmethod
353 def RemapClassName(class_name):
354 """Remaps class names using the jarjar mapping table."""
355 for old, new in JniParams._remappings:
356 if old.endswith('**') and old[:-2] in class_name:
357 return class_name.replace(old[:-2], new, 1)
358 if '*' not in old and class_name.endswith(old):
359 return class_name.replace(old, new, 1)
361 return class_name
363 @staticmethod
364 def SetJarJarMappings(mappings):
365 """Parse jarjar mappings from a string."""
366 JniParams._remappings = []
367 for line in mappings.splitlines():
368 rule = line.split()
369 if rule[0] != 'rule':
370 continue
371 _, src, dest = rule
372 src = src.replace('.', '/')
373 dest = dest.replace('.', '/')
374 if src.endswith('**'):
375 src_real_name = src[:-2]
376 else:
377 assert not '*' in src
378 src_real_name = src
380 if dest.endswith('@0'):
381 JniParams._remappings.append((src, dest[:-2] + src_real_name))
382 elif dest.endswith('@1'):
383 assert '**' in src
384 JniParams._remappings.append((src, dest[:-2]))
385 else:
386 assert not '@' in dest
387 JniParams._remappings.append((src, dest))
390 def ExtractJNINamespace(contents):
391 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
392 m = re.findall(re_jni_namespace, contents)
393 if not m:
394 return ''
395 return m[0]
398 def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
399 re_package = re.compile('.*?package (.*?);')
400 matches = re.findall(re_package, contents)
401 if not matches:
402 raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
403 return (matches[0].replace('.', '/') + '/' +
404 os.path.splitext(os.path.basename(java_file_name))[0])
407 def ExtractNatives(contents, ptr_type):
408 """Returns a list of dict containing information about a native method."""
409 contents = contents.replace('\n', '')
410 natives = []
411 re_native = re.compile(r'(@NativeClassQualifiedName'
412 '\(\"(?P<native_class_name>.*?)\"\)\s+)?'
413 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
414 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
415 '(?P<return_type>\S*) '
416 '(?P<name>native\w+)\((?P<params>.*?)\);')
417 for match in re.finditer(re_native, contents):
418 native = NativeMethod(
419 static='static' in match.group('qualifiers'),
420 java_class_name=match.group('java_class_name'),
421 native_class_name=match.group('native_class_name'),
422 return_type=match.group('return_type'),
423 name=match.group('name').replace('native', ''),
424 params=JniParams.Parse(match.group('params')),
425 ptr_type=ptr_type)
426 natives += [native]
427 return natives
430 def GetStaticCastForReturnType(return_type):
431 type_map = { 'String' : 'jstring',
432 'java/lang/String' : 'jstring',
433 'boolean[]': 'jbooleanArray',
434 'byte[]': 'jbyteArray',
435 'char[]': 'jcharArray',
436 'short[]': 'jshortArray',
437 'int[]': 'jintArray',
438 'long[]': 'jlongArray',
439 'float[]': 'jfloatArray',
440 'double[]': 'jdoubleArray' }
441 ret = type_map.get(return_type, None)
442 if ret:
443 return ret
444 if return_type.endswith('[]'):
445 return 'jobjectArray'
446 return None
449 def GetEnvCall(is_constructor, is_static, return_type):
450 """Maps the types availabe via env->Call__Method."""
451 if is_constructor:
452 return 'NewObject'
453 env_call_map = {'boolean': 'Boolean',
454 'byte': 'Byte',
455 'char': 'Char',
456 'short': 'Short',
457 'int': 'Int',
458 'long': 'Long',
459 'float': 'Float',
460 'void': 'Void',
461 'double': 'Double',
462 'Object': 'Object',
464 call = env_call_map.get(return_type, 'Object')
465 if is_static:
466 call = 'Static' + call
467 return 'Call' + call + 'Method'
470 def GetMangledParam(datatype):
471 """Returns a mangled identifier for the datatype."""
472 if len(datatype) <= 2:
473 return datatype.replace('[', 'A')
474 ret = ''
475 for i in range(1, len(datatype)):
476 c = datatype[i]
477 if c == '[':
478 ret += 'A'
479 elif c.isupper() or datatype[i - 1] in ['/', 'L']:
480 ret += c.upper()
481 return ret
484 def GetMangledMethodName(name, params, return_type):
485 """Returns a mangled method name for the given signature.
487 The returned name can be used as a C identifier and will be unique for all
488 valid overloads of the same method.
490 Args:
491 name: string.
492 params: list of Param.
493 return_type: string.
495 Returns:
496 A mangled name.
498 mangled_items = []
499 for datatype in [return_type] + [x.datatype for x in params]:
500 mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))]
501 mangled_name = name + '_'.join(mangled_items)
502 assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
503 return mangled_name
506 def MangleCalledByNatives(called_by_natives):
507 """Mangles all the overloads from the call_by_natives list."""
508 method_counts = collections.defaultdict(
509 lambda: collections.defaultdict(lambda: 0))
510 for called_by_native in called_by_natives:
511 java_class_name = called_by_native.java_class_name
512 name = called_by_native.name
513 method_counts[java_class_name][name] += 1
514 for called_by_native in called_by_natives:
515 java_class_name = called_by_native.java_class_name
516 method_name = called_by_native.name
517 method_id_var_name = method_name
518 if method_counts[java_class_name][method_name] > 1:
519 method_id_var_name = GetMangledMethodName(method_name,
520 called_by_native.params,
521 called_by_native.return_type)
522 called_by_native.method_id_var_name = method_id_var_name
523 return called_by_natives
526 # Regex to match the JNI return types that should be included in a
527 # ScopedJavaLocalRef.
528 RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array')
530 # Regex to match a string like "@CalledByNative public void foo(int bar)".
531 RE_CALLED_BY_NATIVE = re.compile(
532 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
533 '\s+(?P<prefix>[\w ]*?)'
534 '\s*(?P<return_type>\S+?)'
535 '\s+(?P<name>\w+)'
536 '\s*\((?P<params>[^\)]*)\)')
539 def ExtractCalledByNatives(contents):
540 """Parses all methods annotated with @CalledByNative.
542 Args:
543 contents: the contents of the java file.
545 Returns:
546 A list of dict with information about the annotated methods.
547 TODO(bulach): return a CalledByNative object.
549 Raises:
550 ParseError: if unable to parse.
552 called_by_natives = []
553 for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
554 called_by_natives += [CalledByNative(
555 system_class=False,
556 unchecked='Unchecked' in match.group('Unchecked'),
557 static='static' in match.group('prefix'),
558 java_class_name=match.group('annotation') or '',
559 return_type=match.group('return_type'),
560 name=match.group('name'),
561 params=JniParams.Parse(match.group('params')))]
562 # Check for any @CalledByNative occurrences that weren't matched.
563 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
564 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
565 if '@CalledByNative' in line1:
566 raise ParseError('could not parse @CalledByNative method signature',
567 line1, line2)
568 return MangleCalledByNatives(called_by_natives)
571 class JNIFromJavaP(object):
572 """Uses 'javap' to parse a .class file and generate the JNI header file."""
574 def __init__(self, contents, options):
575 self.contents = contents
576 self.namespace = options.namespace
577 for line in contents:
578 class_name = re.match(
579 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
580 line)
581 if class_name:
582 self.fully_qualified_class = class_name.group('class_name')
583 break
584 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
585 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
586 # away the <...> and use the raw class name that Java 6 would've given us.
587 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
588 JniParams.SetFullyQualifiedClass(self.fully_qualified_class)
589 self.java_class_name = self.fully_qualified_class.split('/')[-1]
590 if not self.namespace:
591 self.namespace = 'JNI_' + self.java_class_name
592 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
593 '\((?P<params>.*?)\)')
594 self.called_by_natives = []
595 for lineno, content in enumerate(contents[2:], 2):
596 match = re.match(re_method, content)
597 if not match:
598 continue
599 self.called_by_natives += [CalledByNative(
600 system_class=True,
601 unchecked=False,
602 static='static' in match.group('prefix'),
603 java_class_name='',
604 return_type=match.group('return_type').replace('.', '/'),
605 name=match.group('name'),
606 params=JniParams.Parse(match.group('params').replace('.', '/')),
607 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
608 re_constructor = re.compile('(.*?)public ' +
609 self.fully_qualified_class.replace('/', '.') +
610 '\((?P<params>.*?)\)')
611 for lineno, content in enumerate(contents[2:], 2):
612 match = re.match(re_constructor, content)
613 if not match:
614 continue
615 self.called_by_natives += [CalledByNative(
616 system_class=True,
617 unchecked=False,
618 static=False,
619 java_class_name='',
620 return_type=self.fully_qualified_class,
621 name='Constructor',
622 params=JniParams.Parse(match.group('params').replace('.', '/')),
623 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
624 is_constructor=True)]
625 self.called_by_natives = MangleCalledByNatives(self.called_by_natives)
627 self.constant_fields = []
628 re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
629 re_constant_field_value = re.compile(
630 '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)')
631 for lineno, content in enumerate(contents[2:], 2):
632 match = re.match(re_constant_field, content)
633 if not match:
634 continue
635 value = re.match(re_constant_field_value, contents[lineno + 2])
636 if not value:
637 value = re.match(re_constant_field_value, contents[lineno + 3])
638 if value:
639 self.constant_fields.append(
640 ConstantField(name=match.group('name'),
641 value=value.group('value')))
643 self.inl_header_file_generator = InlHeaderFileGenerator(
644 self.namespace, self.fully_qualified_class, [],
645 self.called_by_natives, self.constant_fields, options)
647 def GetContent(self):
648 return self.inl_header_file_generator.GetContent()
650 @staticmethod
651 def CreateFromClass(class_file, options):
652 class_name = os.path.splitext(os.path.basename(class_file))[0]
653 p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
654 '-s', class_name],
655 cwd=os.path.dirname(class_file),
656 stdout=subprocess.PIPE,
657 stderr=subprocess.PIPE)
658 stdout, _ = p.communicate()
659 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
660 return jni_from_javap
663 class JNIFromJavaSource(object):
664 """Uses the given java source file to generate the JNI header file."""
666 # Match single line comments, multiline comments, character literals, and
667 # double-quoted strings.
668 _comment_remover_regex = re.compile(
669 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
670 re.DOTALL | re.MULTILINE)
672 def __init__(self, contents, fully_qualified_class, options):
673 contents = self._RemoveComments(contents)
674 JniParams.SetFullyQualifiedClass(fully_qualified_class)
675 JniParams.ExtractImportsAndInnerClasses(contents)
676 jni_namespace = ExtractJNINamespace(contents) or options.namespace
677 natives = ExtractNatives(contents, options.ptr_type)
678 called_by_natives = ExtractCalledByNatives(contents)
679 if len(natives) == 0 and len(called_by_natives) == 0:
680 raise SyntaxError('Unable to find any JNI methods for %s.' %
681 fully_qualified_class)
682 inl_header_file_generator = InlHeaderFileGenerator(
683 jni_namespace, fully_qualified_class, natives, called_by_natives,
684 [], options)
685 self.content = inl_header_file_generator.GetContent()
687 @classmethod
688 def _RemoveComments(cls, contents):
689 # We need to support both inline and block comments, and we need to handle
690 # strings that contain '//' or '/*'.
691 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
692 # parser. Maybe we could ditch JNIFromJavaSource and just always use
693 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
694 # http://code.google.com/p/chromium/issues/detail?id=138941
695 def replacer(match):
696 # Replace matches that are comments with nothing; return literals/strings
697 # unchanged.
698 s = match.group(0)
699 if s.startswith('/'):
700 return ''
701 else:
702 return s
703 return cls._comment_remover_regex.sub(replacer, contents)
705 def GetContent(self):
706 return self.content
708 @staticmethod
709 def CreateFromFile(java_file_name, options):
710 contents = file(java_file_name).read()
711 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
712 contents)
713 return JNIFromJavaSource(contents, fully_qualified_class, options)
716 class InlHeaderFileGenerator(object):
717 """Generates an inline header file for JNI integration."""
719 def __init__(self, namespace, fully_qualified_class, natives,
720 called_by_natives, constant_fields, options):
721 self.namespace = namespace
722 self.fully_qualified_class = fully_qualified_class
723 self.class_name = self.fully_qualified_class.split('/')[-1]
724 self.natives = natives
725 self.called_by_natives = called_by_natives
726 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
727 self.constant_fields = constant_fields
728 self.options = options
729 self.init_native = self.ExtractInitNative(options)
731 def ExtractInitNative(self, options):
732 for native in self.natives:
733 if options.jni_init_native_name == 'native' + native.name:
734 self.natives.remove(native)
735 return native
736 return None
738 def GetContent(self):
739 """Returns the content of the JNI binding file."""
740 template = Template("""\
741 // Copyright 2014 The Chromium Authors. All rights reserved.
742 // Use of this source code is governed by a BSD-style license that can be
743 // found in the LICENSE file.
746 // This file is autogenerated by
747 // ${SCRIPT_NAME}
748 // For
749 // ${FULLY_QUALIFIED_CLASS}
751 #ifndef ${HEADER_GUARD}
752 #define ${HEADER_GUARD}
754 #include <jni.h>
756 ${INCLUDES}
758 #include "base/android/jni_int_wrapper.h"
760 // Step 1: forward declarations.
761 namespace {
762 $CLASS_PATH_DEFINITIONS
763 $METHOD_ID_DEFINITIONS
764 } // namespace
766 $OPEN_NAMESPACE
767 $FORWARD_DECLARATIONS
769 $CONSTANT_FIELDS
771 // Step 2: method stubs.
772 $METHOD_STUBS
774 // Step 3: RegisterNatives.
775 $JNI_NATIVE_METHODS
776 $REGISTER_NATIVES
777 $CLOSE_NAMESPACE
778 $JNI_REGISTER_NATIVES
779 #endif // ${HEADER_GUARD}
780 """)
781 values = {
782 'SCRIPT_NAME': self.options.script_name,
783 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
784 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
785 'METHOD_ID_DEFINITIONS': self.GetMethodIDDefinitionsString(),
786 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(),
787 'CONSTANT_FIELDS': self.GetConstantFieldsString(),
788 'METHOD_STUBS': self.GetMethodStubsString(),
789 'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
790 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(),
791 'REGISTER_NATIVES': self.GetRegisterNativesString(),
792 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
793 'HEADER_GUARD': self.header_guard,
794 'INCLUDES': self.GetIncludesString(),
795 'JNI_REGISTER_NATIVES': self.GetJNIRegisterNativesString()
797 return WrapOutput(template.substitute(values))
799 def GetClassPathDefinitionsString(self):
800 ret = []
801 ret += [self.GetClassPathDefinitions()]
802 return '\n'.join(ret)
804 def GetMethodIDDefinitionsString(self):
805 """Returns the definition of method ids for the called by native methods."""
806 if not self.options.eager_called_by_natives:
807 return ''
808 template = Template("""\
809 jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = NULL;""")
810 ret = []
811 for called_by_native in self.called_by_natives:
812 values = {
813 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
814 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
816 ret += [template.substitute(values)]
817 return '\n'.join(ret)
819 def GetForwardDeclarationsString(self):
820 ret = []
821 for native in self.natives:
822 if native.type != 'method':
823 ret += [self.GetForwardDeclaration(native)]
824 if self.options.native_exports and ret:
825 return '\nextern "C" {\n' + "\n".join(ret) + '\n}; // extern "C"'
826 return '\n'.join(ret)
828 def GetConstantFieldsString(self):
829 if not self.constant_fields:
830 return ''
831 ret = ['enum Java_%s_constant_fields {' % self.class_name]
832 for c in self.constant_fields:
833 ret += [' %s = %s,' % (c.name, c.value)]
834 ret += ['};']
835 return '\n'.join(ret)
837 def GetMethodStubsString(self):
838 """Returns the code corresponding to method stubs."""
839 ret = []
840 for native in self.natives:
841 if native.type == 'method':
842 ret += [self.GetNativeMethodStubString(native)]
843 if self.options.eager_called_by_natives:
844 ret += self.GetEagerCalledByNativeMethodStubs()
845 else:
846 ret += self.GetLazyCalledByNativeMethodStubs()
848 if self.options.native_exports and ret:
849 return '\nextern "C" {\n' + "\n".join(ret) + '\n}; // extern "C"'
850 return '\n'.join(ret)
852 def GetLazyCalledByNativeMethodStubs(self):
853 return [self.GetLazyCalledByNativeMethodStub(called_by_native)
854 for called_by_native in self.called_by_natives]
856 def GetEagerCalledByNativeMethodStubs(self):
857 ret = []
858 if self.called_by_natives:
859 ret += ['namespace {']
860 for called_by_native in self.called_by_natives:
861 ret += [self.GetEagerCalledByNativeMethodStub(called_by_native)]
862 ret += ['} // namespace']
863 return ret
865 def GetIncludesString(self):
866 if not self.options.includes:
867 return ''
868 includes = self.options.includes.split(',')
869 return '\n'.join('#include "%s"' % x for x in includes)
871 def GetKMethodsString(self, clazz):
872 ret = []
873 for native in self.natives:
874 if (native.java_class_name == clazz or
875 (not native.java_class_name and clazz == self.class_name)):
876 ret += [self.GetKMethodArrayEntry(native)]
877 return '\n'.join(ret)
879 def SubstituteNativeMethods(self, template):
880 """Substitutes JAVA_CLASS and KMETHODS in the provided template."""
881 ret = []
882 all_classes = self.GetUniqueClasses(self.natives)
883 all_classes[self.class_name] = self.fully_qualified_class
884 for clazz in all_classes:
885 kmethods = self.GetKMethodsString(clazz)
886 if kmethods:
887 values = {'JAVA_CLASS': clazz,
888 'KMETHODS': kmethods}
889 ret += [template.substitute(values)]
890 if not ret: return ''
891 return '\n' + '\n'.join(ret)
893 def GetJNINativeMethodsString(self):
894 """Returns the implementation of the array of native methods."""
895 if self.options.native_exports and not self.options.native_exports_optional:
896 return ''
897 template = Template("""\
898 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
899 ${KMETHODS}
901 """)
902 return self.SubstituteNativeMethods(template)
904 def GetRegisterCalledByNativesImplString(self):
905 """Returns the code for registering the called by native methods."""
906 if not self.options.eager_called_by_natives:
907 return ''
908 template = Template("""\
909 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = ${GET_METHOD_ID_IMPL}
910 if (g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} == NULL) {
911 return false;
913 """)
914 ret = []
915 for called_by_native in self.called_by_natives:
916 values = {
917 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
918 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
919 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native),
921 ret += [template.substitute(values)]
922 return '\n'.join(ret)
924 def GetRegisterNativesString(self):
925 """Returns the code for RegisterNatives."""
926 template = Template("""\
927 ${REGISTER_NATIVES_SIGNATURE} {
928 ${EARLY_EXIT}
929 ${CLASSES}
930 ${NATIVES}
931 ${CALLED_BY_NATIVES}
932 return true;
934 """)
935 signature = 'static bool RegisterNativesImpl(JNIEnv* env'
936 if self.init_native:
937 signature += ', jclass clazz)'
938 else:
939 signature += ')'
941 early_exit = ''
942 if self.options.native_exports_optional:
943 early_exit = """\
944 if (base::android::IsManualJniRegistrationDisabled()) return true;
947 natives = self.GetRegisterNativesImplString()
948 called_by_natives = self.GetRegisterCalledByNativesImplString()
949 values = {'REGISTER_NATIVES_SIGNATURE': signature,
950 'EARLY_EXIT': early_exit,
951 'CLASSES': self.GetFindClasses(),
952 'NATIVES': natives,
953 'CALLED_BY_NATIVES': called_by_natives,
955 return template.substitute(values)
957 def GetRegisterNativesImplString(self):
958 """Returns the shared implementation for RegisterNatives."""
959 if self.options.native_exports and not self.options.native_exports_optional:
960 return ''
962 template = Template("""\
963 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
965 if (env->RegisterNatives(${JAVA_CLASS}_clazz(env),
966 kMethods${JAVA_CLASS},
967 kMethods${JAVA_CLASS}Size) < 0) {
968 jni_generator::HandleRegistrationError(
969 env, ${JAVA_CLASS}_clazz(env), __FILE__);
970 return false;
972 """)
973 return self.SubstituteNativeMethods(template)
975 def GetJNIRegisterNativesString(self):
976 """Returns the implementation for the JNI registration of native methods."""
977 if not self.init_native:
978 return ''
980 template = Template("""\
981 extern "C" JNIEXPORT bool JNICALL
982 Java_${FULLY_QUALIFIED_CLASS}_${INIT_NATIVE_NAME}(JNIEnv* env, jclass clazz) {
983 return ${NAMESPACE}RegisterNativesImpl(env, clazz);
985 """)
987 if self.options.native_exports:
988 java_name = JniParams.RemapClassName(self.fully_qualified_class)
989 java_name = java_name.replace('_', '_1').replace('/', '_')
990 else:
991 java_name = self.fully_qualified_class.replace('/', '_')
993 namespace = ''
994 if self.namespace:
995 namespace = self.namespace + '::'
996 values = {'FULLY_QUALIFIED_CLASS': java_name,
997 'INIT_NATIVE_NAME': 'native' + self.init_native.name,
998 'NAMESPACE': namespace,
999 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString()
1001 return template.substitute(values)
1003 def GetOpenNamespaceString(self):
1004 if self.namespace:
1005 all_namespaces = ['namespace %s {' % ns
1006 for ns in self.namespace.split('::')]
1007 return '\n'.join(all_namespaces)
1008 return ''
1010 def GetCloseNamespaceString(self):
1011 if self.namespace:
1012 all_namespaces = ['} // namespace %s' % ns
1013 for ns in self.namespace.split('::')]
1014 all_namespaces.reverse()
1015 return '\n'.join(all_namespaces) + '\n'
1016 return ''
1018 def GetJNIFirstParam(self, native):
1019 ret = []
1020 if native.type == 'method':
1021 ret = ['jobject jcaller']
1022 elif native.type == 'function':
1023 if native.static:
1024 ret = ['jclass jcaller']
1025 else:
1026 ret = ['jobject jcaller']
1027 return ret
1029 def GetParamsInDeclaration(self, native):
1030 """Returns the params for the stub declaration.
1032 Args:
1033 native: the native dictionary describing the method.
1035 Returns:
1036 A string containing the params.
1038 return ',\n '.join(self.GetJNIFirstParam(native) +
1039 [JavaDataTypeToC(param.datatype) + ' ' +
1040 param.name
1041 for param in native.params])
1043 def GetCalledByNativeParamsInDeclaration(self, called_by_native):
1044 return ',\n '.join([
1045 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
1046 param.name
1047 for param in called_by_native.params])
1049 def GetStubName(self, native):
1050 """Return the name of the stub function for this native method.
1052 Args:
1053 native: the native dictionary describing the method.
1055 Returns:
1056 A string with the stub function name. For native exports mode this is the
1057 Java_* symbol name required by the JVM; otherwise it is just the name of
1058 the native method itself.
1060 if self.options.native_exports:
1061 template = Template("Java_${JAVA_NAME}_native${NAME}")
1063 java_name = JniParams.RemapClassName(self.fully_qualified_class)
1064 java_name = java_name.replace('_', '_1').replace('/', '_')
1065 if native.java_class_name:
1066 java_name += '_00024' + native.java_class_name
1068 values = {'NAME': native.name,
1069 'JAVA_NAME': java_name}
1070 return template.substitute(values)
1071 else:
1072 return native.name
1074 def GetForwardDeclaration(self, native):
1075 template_str = """
1076 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
1078 if self.options.native_exports:
1079 template_str += """
1080 __attribute__((visibility("default")))
1081 ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS}) {
1082 return ${NAME}(${PARAMS_IN_CALL});
1085 template = Template(template_str)
1086 params_in_call = []
1087 if not self.options.pure_native_methods:
1088 params_in_call = ['env', 'jcaller']
1089 params_in_call = ', '.join(params_in_call + [p.name for p in native.params])
1091 values = {'RETURN': JavaDataTypeToC(native.return_type),
1092 'NAME': native.name,
1093 'PARAMS': self.GetParamsInDeclaration(native),
1094 'PARAMS_IN_CALL': params_in_call,
1095 'STUB_NAME': self.GetStubName(native)}
1096 return template.substitute(values)
1098 def GetNativeMethodStubString(self, native):
1099 """Returns stubs for native methods."""
1100 if self.options.native_exports:
1101 template_str = """\
1102 __attribute__((visibility("default")))
1103 ${RETURN} ${STUB_NAME}(JNIEnv* env,
1104 ${PARAMS_IN_DECLARATION}) {"""
1105 else:
1106 template_str = """\
1107 static ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {"""
1108 template_str += """
1109 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
1110 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
1111 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
1115 template = Template(template_str)
1116 params = []
1117 if not self.options.pure_native_methods:
1118 params = ['env', 'jcaller']
1119 params_in_call = ', '.join(params + [p.name for p in native.params[1:]])
1121 return_type = JavaDataTypeToC(native.return_type)
1122 optional_error_return = JavaReturnValueToC(native.return_type)
1123 if optional_error_return:
1124 optional_error_return = ', ' + optional_error_return
1125 post_call = ''
1126 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1127 post_call = '.Release()'
1129 values = {
1130 'RETURN': return_type,
1131 'OPTIONAL_ERROR_RETURN': optional_error_return,
1132 'NAME': native.name,
1133 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native),
1134 'PARAM0_NAME': native.params[0].name,
1135 'P0_TYPE': native.p0_type,
1136 'PARAMS_IN_CALL': params_in_call,
1137 'POST_CALL': post_call,
1138 'STUB_NAME': self.GetStubName(native),
1140 return template.substitute(values)
1142 def GetArgument(self, param):
1143 return ('as_jint(' + param.name + ')'
1144 if param.datatype == 'int' else param.name)
1146 def GetArgumentsInCall(self, params):
1147 """Return a string of arguments to call from native into Java"""
1148 return [self.GetArgument(p) for p in params]
1150 def GetCalledByNativeValues(self, called_by_native):
1151 """Fills in necessary values for the CalledByNative methods."""
1152 java_class = called_by_native.java_class_name or self.class_name
1153 if called_by_native.static or called_by_native.is_constructor:
1154 first_param_in_declaration = ''
1155 first_param_in_call = ('%s_clazz(env)' % java_class)
1156 else:
1157 first_param_in_declaration = ', jobject obj'
1158 first_param_in_call = 'obj'
1159 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
1160 called_by_native)
1161 if params_in_declaration:
1162 params_in_declaration = ', ' + params_in_declaration
1163 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
1164 if params_in_call:
1165 params_in_call = ', ' + params_in_call
1166 pre_call = ''
1167 post_call = ''
1168 if called_by_native.static_cast:
1169 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
1170 post_call = ')'
1171 check_exception = ''
1172 if not called_by_native.unchecked:
1173 check_exception = 'jni_generator::CheckException(env);'
1174 return_type = JavaDataTypeToC(called_by_native.return_type)
1175 optional_error_return = JavaReturnValueToC(called_by_native.return_type)
1176 if optional_error_return:
1177 optional_error_return = ', ' + optional_error_return
1178 return_declaration = ''
1179 return_clause = ''
1180 if return_type != 'void':
1181 pre_call = ' ' + pre_call
1182 return_declaration = return_type + ' ret ='
1183 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1184 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
1185 return_clause = 'return ' + return_type + '(env, ret);'
1186 else:
1187 return_clause = 'return ret;'
1188 return {
1189 'JAVA_CLASS': java_class,
1190 'RETURN_TYPE': return_type,
1191 'OPTIONAL_ERROR_RETURN': optional_error_return,
1192 'RETURN_DECLARATION': return_declaration,
1193 'RETURN_CLAUSE': return_clause,
1194 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
1195 'PARAMS_IN_DECLARATION': params_in_declaration,
1196 'PRE_CALL': pre_call,
1197 'POST_CALL': post_call,
1198 'ENV_CALL': called_by_native.env_call,
1199 'FIRST_PARAM_IN_CALL': first_param_in_call,
1200 'PARAMS_IN_CALL': params_in_call,
1201 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1202 'CHECK_EXCEPTION': check_exception,
1203 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native)
1206 def GetEagerCalledByNativeMethodStub(self, called_by_native):
1207 """Returns the implementation of the called by native method."""
1208 template = Template("""
1209 static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\
1210 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION}) {
1211 ${RETURN_DECLARATION}${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1212 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL};
1213 ${RETURN_CLAUSE}
1214 }""")
1215 values = self.GetCalledByNativeValues(called_by_native)
1216 return template.substitute(values)
1218 def GetLazyCalledByNativeMethodStub(self, called_by_native):
1219 """Returns a string."""
1220 function_signature_template = Template("""\
1221 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
1222 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
1223 function_header_template = Template("""\
1224 ${FUNCTION_SIGNATURE} {""")
1225 function_header_with_unused_template = Template("""\
1226 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
1227 ${FUNCTION_SIGNATURE} {""")
1228 template = Template("""
1229 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
1230 ${FUNCTION_HEADER}
1231 /* Must call RegisterNativesImpl() */
1232 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
1233 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN});
1234 jmethodID method_id =
1235 ${GET_METHOD_ID_IMPL}
1236 ${RETURN_DECLARATION}
1237 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1238 method_id${PARAMS_IN_CALL})${POST_CALL};
1239 ${CHECK_EXCEPTION}
1240 ${RETURN_CLAUSE}
1241 }""")
1242 values = self.GetCalledByNativeValues(called_by_native)
1243 values['FUNCTION_SIGNATURE'] = (
1244 function_signature_template.substitute(values))
1245 if called_by_native.system_class:
1246 values['FUNCTION_HEADER'] = (
1247 function_header_with_unused_template.substitute(values))
1248 else:
1249 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
1250 return template.substitute(values)
1252 def GetKMethodArrayEntry(self, native):
1253 template = Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' +
1254 'reinterpret_cast<void*>(${STUB_NAME}) },')
1255 values = {'NAME': native.name,
1256 'JNI_SIGNATURE': JniParams.Signature(native.params,
1257 native.return_type,
1258 True),
1259 'STUB_NAME': self.GetStubName(native)}
1260 return template.substitute(values)
1262 def GetUniqueClasses(self, origin):
1263 ret = {self.class_name: self.fully_qualified_class}
1264 for entry in origin:
1265 class_name = self.class_name
1266 jni_class_path = self.fully_qualified_class
1267 if entry.java_class_name:
1268 class_name = entry.java_class_name
1269 jni_class_path = self.fully_qualified_class + '$' + class_name
1270 ret[class_name] = jni_class_path
1271 return ret
1273 def GetClassPathDefinitions(self):
1274 """Returns the ClassPath constants."""
1275 ret = []
1276 template = Template("""\
1277 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
1278 native_classes = self.GetUniqueClasses(self.natives)
1279 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
1280 if self.options.native_exports:
1281 all_classes = called_by_native_classes
1282 else:
1283 all_classes = native_classes
1284 all_classes.update(called_by_native_classes)
1286 for clazz in all_classes:
1287 values = {
1288 'JAVA_CLASS': clazz,
1289 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]),
1291 ret += [template.substitute(values)]
1292 ret += ''
1294 class_getter_methods = []
1295 if self.options.native_exports:
1296 template = Template("""\
1297 // Leaking this jclass as we cannot use LazyInstance from some threads.
1298 base::subtle::AtomicWord g_${JAVA_CLASS}_clazz __attribute__((unused)) = 0;
1299 #define ${JAVA_CLASS}_clazz(env) \
1300 base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \
1301 &g_${JAVA_CLASS}_clazz)""")
1302 else:
1303 template = Template("""\
1304 // Leaking this jclass as we cannot use LazyInstance from some threads.
1305 jclass g_${JAVA_CLASS}_clazz = NULL;
1306 #define ${JAVA_CLASS}_clazz(env) g_${JAVA_CLASS}_clazz""")
1308 for clazz in called_by_native_classes:
1309 values = {
1310 'JAVA_CLASS': clazz,
1312 ret += [template.substitute(values)]
1314 return '\n'.join(ret)
1316 def GetFindClasses(self):
1317 """Returns the imlementation of FindClass for all known classes."""
1318 if self.init_native:
1319 if self.options.native_exports:
1320 template = Template("""\
1321 base::subtle::Release_Store(&g_${JAVA_CLASS}_clazz,
1322 static_cast<base::subtle::AtomicWord>(env->NewWeakGlobalRef(clazz));""")
1323 else:
1324 template = Template("""\
1325 g_${JAVA_CLASS}_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));""")
1326 else:
1327 if self.options.native_exports:
1328 return '\n'
1329 template = Template("""\
1330 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
1331 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
1332 ret = []
1333 for clazz in self.GetUniqueClasses(self.called_by_natives):
1334 values = {'JAVA_CLASS': clazz}
1335 ret += [template.substitute(values)]
1336 return '\n'.join(ret)
1338 def GetMethodIDImpl(self, called_by_native):
1339 """Returns the implementation of GetMethodID."""
1340 if self.options.eager_called_by_natives:
1341 template = Template("""\
1342 env->Get${STATIC_METHOD_PART}MethodID(
1343 ${JAVA_CLASS}_clazz(env),
1344 "${JNI_NAME}", ${JNI_SIGNATURE});""")
1345 else:
1346 template = Template("""\
1347 base::android::MethodID::LazyGet<
1348 base::android::MethodID::TYPE_${STATIC}>(
1349 env, ${JAVA_CLASS}_clazz(env),
1350 "${JNI_NAME}",
1351 ${JNI_SIGNATURE},
1352 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
1353 """)
1354 jni_name = called_by_native.name
1355 jni_return_type = called_by_native.return_type
1356 if called_by_native.is_constructor:
1357 jni_name = '<init>'
1358 jni_return_type = 'void'
1359 if called_by_native.signature:
1360 signature = called_by_native.signature
1361 else:
1362 signature = JniParams.Signature(called_by_native.params,
1363 jni_return_type,
1364 True)
1365 values = {
1366 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
1367 'JNI_NAME': jni_name,
1368 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1369 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
1370 'STATIC_METHOD_PART': 'Static' if called_by_native.static else '',
1371 'JNI_SIGNATURE': signature,
1373 return template.substitute(values)
1376 def WrapOutput(output):
1377 ret = []
1378 for line in output.splitlines():
1379 # Do not wrap lines under 80 characters or preprocessor directives.
1380 if len(line) < 80 or line.lstrip()[:1] == '#':
1381 stripped = line.rstrip()
1382 if len(ret) == 0 or len(ret[-1]) or len(stripped):
1383 ret.append(stripped)
1384 else:
1385 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
1386 subsequent_indent = first_line_indent + ' ' * 4
1387 if line.startswith('//'):
1388 subsequent_indent = '//' + subsequent_indent
1389 wrapper = textwrap.TextWrapper(width=80,
1390 subsequent_indent=subsequent_indent,
1391 break_long_words=False)
1392 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
1393 ret += ['']
1394 return '\n'.join(ret)
1397 def ExtractJarInputFile(jar_file, input_file, out_dir):
1398 """Extracts input file from jar and returns the filename.
1400 The input file is extracted to the same directory that the generated jni
1401 headers will be placed in. This is passed as an argument to script.
1403 Args:
1404 jar_file: the jar file containing the input files to extract.
1405 input_files: the list of files to extract from the jar file.
1406 out_dir: the name of the directories to extract to.
1408 Returns:
1409 the name of extracted input file.
1411 jar_file = zipfile.ZipFile(jar_file)
1413 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
1414 try:
1415 os.makedirs(out_dir)
1416 except OSError as e:
1417 if e.errno != errno.EEXIST:
1418 raise
1419 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
1420 with open(extracted_file_name, 'w') as outfile:
1421 outfile.write(jar_file.read(input_file))
1423 return extracted_file_name
1426 def GenerateJNIHeader(input_file, output_file, options):
1427 try:
1428 if os.path.splitext(input_file)[1] == '.class':
1429 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
1430 content = jni_from_javap.GetContent()
1431 else:
1432 jni_from_java_source = JNIFromJavaSource.CreateFromFile(
1433 input_file, options)
1434 content = jni_from_java_source.GetContent()
1435 except ParseError, e:
1436 print e
1437 sys.exit(1)
1438 if output_file:
1439 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
1440 os.makedirs(os.path.dirname(os.path.abspath(output_file)))
1441 if options.optimize_generation and os.path.exists(output_file):
1442 with file(output_file, 'r') as f:
1443 existing_content = f.read()
1444 if existing_content == content:
1445 return
1446 with file(output_file, 'w') as f:
1447 f.write(content)
1448 else:
1449 print content
1452 def GetScriptName():
1453 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
1454 base_index = 0
1455 for idx, value in enumerate(script_components):
1456 if value == 'base' or value == 'third_party':
1457 base_index = idx
1458 break
1459 return os.sep.join(script_components[base_index:])
1462 def main(argv):
1463 usage = """usage: %prog [OPTIONS]
1464 This script will parse the given java source code extracting the native
1465 declarations and print the header file to stdout (or a file).
1466 See SampleForTests.java for more details.
1468 option_parser = optparse.OptionParser(usage=usage)
1469 build_utils.AddDepfileOption(option_parser)
1471 option_parser.add_option('-j', '--jar_file', dest='jar_file',
1472 help='Extract the list of input files from'
1473 ' a specified jar file.'
1474 ' Uses javap to extract the methods from a'
1475 ' pre-compiled class. --input should point'
1476 ' to pre-compiled Java .class files.')
1477 option_parser.add_option('-n', dest='namespace',
1478 help='Uses as a namespace in the generated header '
1479 'instead of the javap class name, or when there is '
1480 'no JNINamespace annotation in the java source.')
1481 option_parser.add_option('--input_file',
1482 help='Single input file name. The output file name '
1483 'will be derived from it. Must be used with '
1484 '--output_dir.')
1485 option_parser.add_option('--output_dir',
1486 help='The output directory. Must be used with '
1487 '--input')
1488 option_parser.add_option('--optimize_generation', type="int",
1489 default=0, help='Whether we should optimize JNI '
1490 'generation by not regenerating files if they have '
1491 'not changed.')
1492 option_parser.add_option('--jarjar',
1493 help='Path to optional jarjar rules file.')
1494 option_parser.add_option('--script_name', default=GetScriptName(),
1495 help='The name of this script in the generated '
1496 'header.')
1497 option_parser.add_option('--includes',
1498 help='The comma-separated list of header files to '
1499 'include in the generated header.')
1500 option_parser.add_option('--pure_native_methods',
1501 action='store_true', dest='pure_native_methods',
1502 help='When true, the native methods will be called '
1503 'without any JNI-specific arguments.')
1504 option_parser.add_option('--ptr_type', default='int',
1505 type='choice', choices=['int', 'long'],
1506 help='The type used to represent native pointers in '
1507 'Java code. For 32-bit, use int; '
1508 'for 64-bit, use long.')
1509 option_parser.add_option('--jni_init_native_name', default='',
1510 help='The name of the JNI registration method that '
1511 'is used to initialize all native methods. If a '
1512 'method with this name is not present in the Java '
1513 'source file, setting this option is a no-op. When '
1514 'a method with this name is found however, the '
1515 'naming convention Java_<packageName>_<className> '
1516 'will limit the initialization to only the '
1517 'top-level class.')
1518 option_parser.add_option('--eager_called_by_natives',
1519 action='store_true', dest='eager_called_by_natives',
1520 help='When true, the called-by-native methods will '
1521 'be initialized in a non-atomic way.')
1522 option_parser.add_option('--cpp', default='cpp',
1523 help='The path to cpp command.')
1524 option_parser.add_option('--javap', default='javap',
1525 help='The path to javap command.')
1526 option_parser.add_option('--native_exports', action='store_true',
1527 help='Native method registration through .so '
1528 'exports.')
1529 option_parser.add_option('--native_exports_optional', action='store_true',
1530 help='Support both explicit and native method'
1531 'registration.')
1532 options, args = option_parser.parse_args(argv)
1533 if options.native_exports_optional:
1534 options.native_exports = True
1535 if options.jar_file:
1536 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1537 options.output_dir)
1538 elif options.input_file:
1539 input_file = options.input_file
1540 else:
1541 option_parser.print_help()
1542 print '\nError: Must specify --jar_file or --input_file.'
1543 return 1
1544 output_file = None
1545 if options.output_dir:
1546 root_name = os.path.splitext(os.path.basename(input_file))[0]
1547 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
1548 if options.jarjar:
1549 with open(options.jarjar) as f:
1550 JniParams.SetJarJarMappings(f.read())
1551 GenerateJNIHeader(input_file, output_file, options)
1553 if options.depfile:
1554 build_utils.WriteDepfile(
1555 options.depfile,
1556 build_utils.GetPythonDependencies())
1559 if __name__ == '__main__':
1560 sys.exit(main(sys.argv))