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