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