Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / base / android / jni_generator / jni_generator.py
blob4342fed69a47dcb71fe32842817c1c84e6fc7b01
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")))
1041 ${RETURN} Java_${JAVA_NAME}_native${NAME}(JNIEnv* env, ${PARAMS}) {
1042 return ${NAME}(${PARAMS_IN_CALL});
1045 template = Template(template_str)
1046 params_in_call = []
1047 if not self.options.pure_native_methods:
1048 params_in_call = ['env', 'jcaller']
1049 params_in_call = ', '.join(params_in_call + [p.name for p in native.params])
1051 java_name = JniParams.RemapClassName(self.fully_qualified_class)
1052 java_name = java_name.replace('_', '_1').replace('/', '_')
1053 if native.java_class_name:
1054 java_name += '_00024' + native.java_class_name
1056 values = {'RETURN': JavaDataTypeToC(native.return_type),
1057 'NAME': native.name,
1058 'JAVA_NAME': java_name,
1059 'PARAMS': self.GetParamsInDeclaration(native),
1060 'PARAMS_IN_CALL': params_in_call}
1061 return template.substitute(values)
1063 def GetNativeMethodStubString(self, native):
1064 """Returns stubs for native methods."""
1065 if self.options.native_exports:
1066 template_str = """\
1067 __attribute__((visibility("default")))
1068 ${RETURN} Java_${JAVA_NAME}_native${NAME}(JNIEnv* env,
1069 ${PARAMS_IN_DECLARATION}) {"""
1070 else:
1071 template_str = """\
1072 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {"""
1073 template_str += """
1074 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
1075 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
1076 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
1080 template = Template(template_str)
1081 params = []
1082 if not self.options.pure_native_methods:
1083 params = ['env', 'jcaller']
1084 params_in_call = ', '.join(params + [p.name for p in native.params[1:]])
1086 return_type = JavaDataTypeToC(native.return_type)
1087 optional_error_return = JavaReturnValueToC(native.return_type)
1088 if optional_error_return:
1089 optional_error_return = ', ' + optional_error_return
1090 post_call = ''
1091 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1092 post_call = '.Release()'
1094 if self.options.native_exports:
1095 java_name = JniParams.RemapClassName(self.fully_qualified_class)
1096 java_name = java_name.replace('_', '_1').replace('/', '_')
1097 if native.java_class_name:
1098 java_name += '_00024' + native.java_class_name
1099 else:
1100 java_name = ''
1102 values = {
1103 'RETURN': return_type,
1104 'OPTIONAL_ERROR_RETURN': optional_error_return,
1105 'JAVA_NAME': java_name,
1106 'NAME': native.name,
1107 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native),
1108 'PARAM0_NAME': native.params[0].name,
1109 'P0_TYPE': native.p0_type,
1110 'PARAMS_IN_CALL': params_in_call,
1111 'POST_CALL': post_call
1113 return template.substitute(values)
1115 def GetArgument(self, param):
1116 return ('as_jint(' + param.name + ')'
1117 if param.datatype == 'int' else param.name)
1119 def GetArgumentsInCall(self, params):
1120 """Return a string of arguments to call from native into Java"""
1121 return [self.GetArgument(p) for p in params]
1123 def GetCalledByNativeValues(self, called_by_native):
1124 """Fills in necessary values for the CalledByNative methods."""
1125 java_class = called_by_native.java_class_name or self.class_name
1126 if called_by_native.static or called_by_native.is_constructor:
1127 first_param_in_declaration = ''
1128 first_param_in_call = ('%s_clazz(env)' % java_class)
1129 else:
1130 first_param_in_declaration = ', jobject obj'
1131 first_param_in_call = 'obj'
1132 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
1133 called_by_native)
1134 if params_in_declaration:
1135 params_in_declaration = ', ' + params_in_declaration
1136 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
1137 if params_in_call:
1138 params_in_call = ', ' + params_in_call
1139 pre_call = ''
1140 post_call = ''
1141 if called_by_native.static_cast:
1142 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
1143 post_call = ')'
1144 check_exception = ''
1145 if not called_by_native.unchecked:
1146 check_exception = 'jni_generator::CheckException(env);'
1147 return_type = JavaDataTypeToC(called_by_native.return_type)
1148 optional_error_return = JavaReturnValueToC(called_by_native.return_type)
1149 if optional_error_return:
1150 optional_error_return = ', ' + optional_error_return
1151 return_declaration = ''
1152 return_clause = ''
1153 if return_type != 'void':
1154 pre_call = ' ' + pre_call
1155 return_declaration = return_type + ' ret ='
1156 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1157 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
1158 return_clause = 'return ' + return_type + '(env, ret);'
1159 else:
1160 return_clause = 'return ret;'
1161 return {
1162 'JAVA_CLASS': java_class,
1163 'RETURN_TYPE': return_type,
1164 'OPTIONAL_ERROR_RETURN': optional_error_return,
1165 'RETURN_DECLARATION': return_declaration,
1166 'RETURN_CLAUSE': return_clause,
1167 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
1168 'PARAMS_IN_DECLARATION': params_in_declaration,
1169 'PRE_CALL': pre_call,
1170 'POST_CALL': post_call,
1171 'ENV_CALL': called_by_native.env_call,
1172 'FIRST_PARAM_IN_CALL': first_param_in_call,
1173 'PARAMS_IN_CALL': params_in_call,
1174 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1175 'CHECK_EXCEPTION': check_exception,
1176 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native)
1179 def GetEagerCalledByNativeMethodStub(self, called_by_native):
1180 """Returns the implementation of the called by native method."""
1181 template = Template("""
1182 static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\
1183 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION}) {
1184 ${RETURN_DECLARATION}${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1185 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL};
1186 ${RETURN_CLAUSE}
1187 }""")
1188 values = self.GetCalledByNativeValues(called_by_native)
1189 return template.substitute(values)
1191 def GetLazyCalledByNativeMethodStub(self, called_by_native):
1192 """Returns a string."""
1193 function_signature_template = Template("""\
1194 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
1195 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
1196 function_header_template = Template("""\
1197 ${FUNCTION_SIGNATURE} {""")
1198 function_header_with_unused_template = Template("""\
1199 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
1200 ${FUNCTION_SIGNATURE} {""")
1201 template = Template("""
1202 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
1203 ${FUNCTION_HEADER}
1204 /* Must call RegisterNativesImpl() */
1205 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
1206 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN});
1207 jmethodID method_id =
1208 ${GET_METHOD_ID_IMPL}
1209 ${RETURN_DECLARATION}
1210 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1211 method_id${PARAMS_IN_CALL})${POST_CALL};
1212 ${CHECK_EXCEPTION}
1213 ${RETURN_CLAUSE}
1214 }""")
1215 values = self.GetCalledByNativeValues(called_by_native)
1216 values['FUNCTION_SIGNATURE'] = (
1217 function_signature_template.substitute(values))
1218 if called_by_native.system_class:
1219 values['FUNCTION_HEADER'] = (
1220 function_header_with_unused_template.substitute(values))
1221 else:
1222 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
1223 return template.substitute(values)
1225 def GetKMethodArrayEntry(self, native):
1226 template = Template("""\
1227 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""")
1228 values = {'NAME': native.name,
1229 'JNI_SIGNATURE': JniParams.Signature(native.params,
1230 native.return_type,
1231 True)}
1232 return template.substitute(values)
1234 def GetUniqueClasses(self, origin):
1235 ret = {self.class_name: self.fully_qualified_class}
1236 for entry in origin:
1237 class_name = self.class_name
1238 jni_class_path = self.fully_qualified_class
1239 if entry.java_class_name:
1240 class_name = entry.java_class_name
1241 jni_class_path = self.fully_qualified_class + '$' + class_name
1242 ret[class_name] = jni_class_path
1243 return ret
1245 def GetClassPathDefinitions(self):
1246 """Returns the ClassPath constants."""
1247 ret = []
1248 template = Template("""\
1249 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
1250 native_classes = self.GetUniqueClasses(self.natives)
1251 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
1252 if self.options.native_exports:
1253 all_classes = called_by_native_classes
1254 else:
1255 all_classes = native_classes
1256 all_classes.update(called_by_native_classes)
1258 for clazz in all_classes:
1259 values = {
1260 'JAVA_CLASS': clazz,
1261 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]),
1263 ret += [template.substitute(values)]
1264 ret += ''
1266 class_getter_methods = []
1267 if self.options.native_exports:
1268 template = Template("""\
1269 // Leaking this jclass as we cannot use LazyInstance from some threads.
1270 base::subtle::AtomicWord g_${JAVA_CLASS}_clazz = 0;
1271 #define ${JAVA_CLASS}_clazz(env) \
1272 base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \
1273 &g_${JAVA_CLASS}_clazz)""")
1274 else:
1275 template = Template("""\
1276 // Leaking this jclass as we cannot use LazyInstance from some threads.
1277 jclass g_${JAVA_CLASS}_clazz = NULL;
1278 #define ${JAVA_CLASS}_clazz(env) g_${JAVA_CLASS}_clazz""")
1280 for clazz in called_by_native_classes:
1281 values = {
1282 'JAVA_CLASS': clazz,
1284 ret += [template.substitute(values)]
1286 return '\n'.join(ret)
1288 def GetFindClasses(self):
1289 """Returns the imlementation of FindClass for all known classes."""
1290 if self.init_native:
1291 if self.options.native_exports:
1292 template = Template("""\
1293 base::subtle::Release_Store(&g_${JAVA_CLASS}_clazz,
1294 static_cast<base::subtle::AtomicWord>(env->NewWeakGlobalRef(clazz));""")
1295 else:
1296 template = Template("""\
1297 g_${JAVA_CLASS}_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));""")
1298 else:
1299 if self.options.native_exports:
1300 return '\n'
1301 template = Template("""\
1302 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
1303 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
1304 ret = []
1305 for clazz in self.GetUniqueClasses(self.called_by_natives):
1306 values = {'JAVA_CLASS': clazz}
1307 ret += [template.substitute(values)]
1308 return '\n'.join(ret)
1310 def GetMethodIDImpl(self, called_by_native):
1311 """Returns the implementation of GetMethodID."""
1312 if self.options.eager_called_by_natives:
1313 template = Template("""\
1314 env->Get${STATIC_METHOD_PART}MethodID(
1315 ${JAVA_CLASS}_clazz(env),
1316 "${JNI_NAME}", ${JNI_SIGNATURE});""")
1317 else:
1318 template = Template("""\
1319 base::android::MethodID::LazyGet<
1320 base::android::MethodID::TYPE_${STATIC}>(
1321 env, ${JAVA_CLASS}_clazz(env),
1322 "${JNI_NAME}",
1323 ${JNI_SIGNATURE},
1324 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
1325 """)
1326 jni_name = called_by_native.name
1327 jni_return_type = called_by_native.return_type
1328 if called_by_native.is_constructor:
1329 jni_name = '<init>'
1330 jni_return_type = 'void'
1331 if called_by_native.signature:
1332 signature = called_by_native.signature
1333 else:
1334 signature = JniParams.Signature(called_by_native.params,
1335 jni_return_type,
1336 True)
1337 values = {
1338 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
1339 'JNI_NAME': jni_name,
1340 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1341 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
1342 'STATIC_METHOD_PART': 'Static' if called_by_native.static else '',
1343 'JNI_SIGNATURE': signature,
1345 return template.substitute(values)
1348 def WrapOutput(output):
1349 ret = []
1350 for line in output.splitlines():
1351 # Do not wrap lines under 80 characters or preprocessor directives.
1352 if len(line) < 80 or line.lstrip()[:1] == '#':
1353 stripped = line.rstrip()
1354 if len(ret) == 0 or len(ret[-1]) or len(stripped):
1355 ret.append(stripped)
1356 else:
1357 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
1358 subsequent_indent = first_line_indent + ' ' * 4
1359 if line.startswith('//'):
1360 subsequent_indent = '//' + subsequent_indent
1361 wrapper = textwrap.TextWrapper(width=80,
1362 subsequent_indent=subsequent_indent,
1363 break_long_words=False)
1364 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
1365 ret += ['']
1366 return '\n'.join(ret)
1369 def ExtractJarInputFile(jar_file, input_file, out_dir):
1370 """Extracts input file from jar and returns the filename.
1372 The input file is extracted to the same directory that the generated jni
1373 headers will be placed in. This is passed as an argument to script.
1375 Args:
1376 jar_file: the jar file containing the input files to extract.
1377 input_files: the list of files to extract from the jar file.
1378 out_dir: the name of the directories to extract to.
1380 Returns:
1381 the name of extracted input file.
1383 jar_file = zipfile.ZipFile(jar_file)
1385 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
1386 try:
1387 os.makedirs(out_dir)
1388 except OSError as e:
1389 if e.errno != errno.EEXIST:
1390 raise
1391 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
1392 with open(extracted_file_name, 'w') as outfile:
1393 outfile.write(jar_file.read(input_file))
1395 return extracted_file_name
1398 def GenerateJNIHeader(input_file, output_file, options):
1399 try:
1400 if os.path.splitext(input_file)[1] == '.class':
1401 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
1402 content = jni_from_javap.GetContent()
1403 else:
1404 jni_from_java_source = JNIFromJavaSource.CreateFromFile(
1405 input_file, options)
1406 content = jni_from_java_source.GetContent()
1407 except ParseError, e:
1408 print e
1409 sys.exit(1)
1410 if output_file:
1411 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
1412 os.makedirs(os.path.dirname(os.path.abspath(output_file)))
1413 if options.optimize_generation and os.path.exists(output_file):
1414 with file(output_file, 'r') as f:
1415 existing_content = f.read()
1416 if existing_content == content:
1417 return
1418 with file(output_file, 'w') as f:
1419 f.write(content)
1420 else:
1421 print output
1424 def GetScriptName():
1425 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
1426 base_index = 0
1427 for idx, value in enumerate(script_components):
1428 if value == 'base' or value == 'third_party':
1429 base_index = idx
1430 break
1431 return os.sep.join(script_components[base_index:])
1434 def main(argv):
1435 usage = """usage: %prog [OPTIONS]
1436 This script will parse the given java source code extracting the native
1437 declarations and print the header file to stdout (or a file).
1438 See SampleForTests.java for more details.
1440 option_parser = optparse.OptionParser(usage=usage)
1441 build_utils.AddDepfileOption(option_parser)
1443 option_parser.add_option('-j', '--jar_file', dest='jar_file',
1444 help='Extract the list of input files from'
1445 ' a specified jar file.'
1446 ' Uses javap to extract the methods from a'
1447 ' pre-compiled class. --input should point'
1448 ' to pre-compiled Java .class files.')
1449 option_parser.add_option('-n', dest='namespace',
1450 help='Uses as a namespace in the generated header '
1451 'instead of the javap class name, or when there is '
1452 'no JNINamespace annotation in the java source.')
1453 option_parser.add_option('--input_file',
1454 help='Single input file name. The output file name '
1455 'will be derived from it. Must be used with '
1456 '--output_dir.')
1457 option_parser.add_option('--output_dir',
1458 help='The output directory. Must be used with '
1459 '--input')
1460 option_parser.add_option('--optimize_generation', type="int",
1461 default=0, help='Whether we should optimize JNI '
1462 'generation by not regenerating files if they have '
1463 'not changed.')
1464 option_parser.add_option('--jarjar',
1465 help='Path to optional jarjar rules file.')
1466 option_parser.add_option('--script_name', default=GetScriptName(),
1467 help='The name of this script in the generated '
1468 'header.')
1469 option_parser.add_option('--includes',
1470 help='The comma-separated list of header files to '
1471 'include in the generated header.')
1472 option_parser.add_option('--pure_native_methods',
1473 action='store_true', dest='pure_native_methods',
1474 help='When true, the native methods will be called '
1475 'without any JNI-specific arguments.')
1476 option_parser.add_option('--ptr_type', default='int',
1477 type='choice', choices=['int', 'long'],
1478 help='The type used to represent native pointers in '
1479 'Java code. For 32-bit, use int; '
1480 'for 64-bit, use long.')
1481 option_parser.add_option('--jni_init_native_name', default='',
1482 help='The name of the JNI registration method that '
1483 'is used to initialize all native methods. If a '
1484 'method with this name is not present in the Java '
1485 'source file, setting this option is a no-op. When '
1486 'a method with this name is found however, the '
1487 'naming convention Java_<packageName>_<className> '
1488 'will limit the initialization to only the '
1489 'top-level class.')
1490 option_parser.add_option('--eager_called_by_natives',
1491 action='store_true', dest='eager_called_by_natives',
1492 help='When true, the called-by-native methods will '
1493 'be initialized in a non-atomic way.')
1494 option_parser.add_option('--cpp', default='cpp',
1495 help='The path to cpp command.')
1496 option_parser.add_option('--javap', default='javap',
1497 help='The path to javap command.')
1498 option_parser.add_option('--native_exports', action='store_true',
1499 help='Native method registration through .so '
1500 'exports.')
1501 options, args = option_parser.parse_args(argv)
1502 if options.jar_file:
1503 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1504 options.output_dir)
1505 elif options.input_file:
1506 input_file = options.input_file
1507 else:
1508 option_parser.print_help()
1509 print '\nError: Must specify --jar_file or --input_file.'
1510 return 1
1511 output_file = None
1512 if options.output_dir:
1513 root_name = os.path.splitext(os.path.basename(input_file))[0]
1514 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
1515 if options.jarjar:
1516 with open(options.jarjar) as f:
1517 JniParams.SetJarJarMappings(f.read())
1518 GenerateJNIHeader(input_file, output_file, options)
1520 if options.depfile:
1521 build_utils.WriteDepfile(
1522 options.depfile,
1523 build_utils.GetPythonDependencies())
1526 if __name__ == '__main__':
1527 sys.exit(main(sys.argv))