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
22 class ParseError(Exception):
23 """Exception thrown when we can't parse the input file."""
25 def __init__(self
, description
, *context_lines
):
26 Exception.__init
__(self
)
27 self
.description
= description
28 self
.context_lines
= context_lines
31 context
= '\n'.join(self
.context_lines
)
32 return '***\nERROR: %s\n\n%s\n***' % (self
.description
, context
)
36 """Describes a param for a method, either java or native."""
38 def __init__(self
, **kwargs
):
39 self
.datatype
= kwargs
['datatype']
40 self
.name
= kwargs
['name']
43 class NativeMethod(object):
44 """Describes a C/C++ method that is called by Java code"""
46 def __init__(self
, **kwargs
):
47 self
.static
= kwargs
['static']
48 self
.java_class_name
= kwargs
['java_class_name']
49 self
.return_type
= kwargs
['return_type']
50 self
.name
= kwargs
['name']
51 self
.params
= kwargs
['params']
53 assert type(self
.params
) is list
54 assert type(self
.params
[0]) is Param
56 self
.params
[0].datatype
== 'int' and
57 self
.params
[0].name
.startswith('native')):
59 self
.p0_type
= self
.params
[0].name
[len('native'):]
60 if kwargs
.get('native_class_name'):
61 self
.p0_type
= kwargs
['native_class_name']
63 self
.type = 'function'
64 self
.method_id_var_name
= kwargs
.get('method_id_var_name', None)
67 class CalledByNative(object):
68 """Describes a java method exported to c/c++"""
70 def __init__(self
, **kwargs
):
71 self
.system_class
= kwargs
['system_class']
72 self
.unchecked
= kwargs
['unchecked']
73 self
.static
= kwargs
['static']
74 self
.java_class_name
= kwargs
['java_class_name']
75 self
.return_type
= kwargs
['return_type']
76 self
.name
= kwargs
['name']
77 self
.params
= kwargs
['params']
78 self
.method_id_var_name
= kwargs
.get('method_id_var_name', None)
79 self
.is_constructor
= kwargs
.get('is_constructor', False)
80 self
.env_call
= GetEnvCall(self
.is_constructor
, self
.static
,
82 self
.static_cast
= GetStaticCastForReturnType(self
.return_type
)
85 def JavaDataTypeToC(java_type
):
86 """Returns a C datatype for the given java type."""
92 'boolean': 'jboolean',
100 'java/lang/String': 'jstring',
102 'java/lang/Class': 'jclass',
105 if java_type
in java_pod_type_map
:
106 return java_pod_type_map
[java_type
]
107 elif java_type
in java_type_map
:
108 return java_type_map
[java_type
]
109 elif java_type
.endswith('[]'):
110 if java_type
[:-2] in java_pod_type_map
:
111 return java_pod_type_map
[java_type
[:-2]] + 'Array'
112 return 'jobjectArray'
117 class JniParams(object):
119 _fully_qualified_class
= ''
125 def SetFullyQualifiedClass(fully_qualified_class
):
126 JniParams
._fully
_qualified
_class
= 'L' + fully_qualified_class
127 JniParams
._package
= '/'.join(fully_qualified_class
.split('/')[:-1])
130 def ExtractImportsAndInnerClasses(contents
):
131 contents
= contents
.replace('\n', '')
132 re_import
= re
.compile(r
'import.*?(?P<class>\S*?);')
133 for match
in re
.finditer(re_import
, contents
):
134 JniParams
._imports
+= ['L' + match
.group('class').replace('.', '/')]
136 re_inner
= re
.compile(r
'(class|interface)\s+?(?P<name>\w+?)\W')
137 for match
in re
.finditer(re_inner
, contents
):
138 inner
= match
.group('name')
139 if not JniParams
._fully
_qualified
_class
.endswith(inner
):
140 JniParams
._inner
_classes
+= [JniParams
._fully
_qualified
_class
+ '$' +
144 def JavaToJni(param
):
145 """Converts a java param into a JNI signature type."""
157 object_param_list
= [
158 'Ljava/lang/Boolean',
159 'Ljava/lang/Integer',
167 while param
[-2:] == '[]':
172 param
= param
[:param
.index('<')]
173 if param
in pod_param_map
:
174 return prefix
+ pod_param_map
[param
]
176 # Coming from javap, use the fully qualified param directly.
177 return prefix
+ 'L' + JniParams
.RemapClassName(param
) + ';'
178 for qualified_name
in (object_param_list
+
179 [JniParams
._fully
_qualified
_class
] +
180 JniParams
._inner
_classes
):
181 if (qualified_name
.endswith('/' + param
) or
182 qualified_name
.endswith('$' + param
.replace('.', '$')) or
183 qualified_name
== 'L' + param
):
184 return prefix
+ JniParams
.RemapClassName(qualified_name
) + ';'
186 # Is it from an import? (e.g. referecing Class from import pkg.Class;
187 # note that referencing an inner class Inner from import pkg.Class.Inner
189 for qualified_name
in JniParams
._imports
:
190 if qualified_name
.endswith('/' + param
):
191 # Ensure it's not an inner class.
192 components
= qualified_name
.split('/')
193 if len(components
) > 2 and components
[-2][0].isupper():
194 raise SyntaxError('Inner class (%s) can not be imported '
195 'and used by JNI (%s). Please import the outer '
196 'class and use Outer.Inner instead.' %
197 (qualified_name
, param
))
198 return prefix
+ JniParams
.RemapClassName(qualified_name
) + ';'
200 # Is it an inner class from an outer class import? (e.g. referencing
201 # Class.Inner from import pkg.Class).
203 components
= param
.split('.')
204 outer
= '/'.join(components
[:-1])
205 inner
= components
[-1]
206 for qualified_name
in JniParams
._imports
:
207 if qualified_name
.endswith('/' + outer
):
208 return (prefix
+ JniParams
.RemapClassName(qualified_name
) +
211 # Type not found, falling back to same package as this class.
212 return (prefix
+ 'L' +
213 JniParams
.RemapClassName(JniParams
._package
+ '/' + param
) + ';')
216 def Signature(params
, returns
, wrap
):
217 """Returns the JNI signature for the given datatypes."""
219 items
+= [JniParams
.JavaToJni(param
.datatype
) for param
in params
]
221 items
+= [JniParams
.JavaToJni(returns
)]
223 return '\n' + '\n'.join(['"' + item
+ '"' for item
in items
])
225 return '"' + ''.join(items
) + '"'
229 """Parses the params into a list of Param objects."""
233 for p
in [p
.strip() for p
in params
.split(',')]:
236 items
.remove('final')
239 name
=(items
[1] if len(items
) > 1 else 'p%s' % len(ret
)),
245 def RemapClassName(class_name
):
246 """Remaps class names using the jarjar mapping table."""
247 for old
, new
in JniParams
._remappings
:
248 if old
in class_name
:
249 return class_name
.replace(old
, new
, 1)
253 def SetJarJarMappings(mappings
):
254 """Parse jarjar mappings from a string."""
255 JniParams
._remappings
= []
256 for line
in mappings
.splitlines():
257 keyword
, src
, dest
= line
.split()
258 if keyword
!= 'rule':
260 assert src
.endswith('.**')
261 src
= src
[:-2].replace('.', '/')
262 dest
= dest
.replace('.', '/')
263 if dest
.endswith('@0'):
264 JniParams
._remappings
.append((src
, dest
[:-2] + src
))
266 assert dest
.endswith('@1')
267 JniParams
._remappings
.append((src
, dest
[:-2]))
270 def ExtractJNINamespace(contents
):
271 re_jni_namespace
= re
.compile('.*?@JNINamespace\("(.*?)"\)')
272 m
= re
.findall(re_jni_namespace
, contents
)
278 def ExtractFullyQualifiedJavaClassName(java_file_name
, contents
):
279 re_package
= re
.compile('.*?package (.*?);')
280 matches
= re
.findall(re_package
, contents
)
282 raise SyntaxError('Unable to find "package" line in %s' % java_file_name
)
283 return (matches
[0].replace('.', '/') + '/' +
284 os
.path
.splitext(os
.path
.basename(java_file_name
))[0])
287 def ExtractNatives(contents
):
288 """Returns a list of dict containing information about a native method."""
289 contents
= contents
.replace('\n', '')
291 re_native
= re
.compile(r
'(@NativeClassQualifiedName'
292 '\(\"(?P<native_class_name>.*?)\"\))?\s*'
293 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\)))?\s*'
294 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*?native '
295 '(?P<return_type>\S*?) '
296 '(?P<name>\w+?)\((?P<params>.*?)\);')
297 for match
in re
.finditer(re_native
, contents
):
298 native
= NativeMethod(
299 static
='static' in match
.group('qualifiers'),
300 java_class_name
=match
.group('java_class_name'),
301 native_class_name
=match
.group('native_class_name'),
302 return_type
=match
.group('return_type'),
303 name
=match
.group('name').replace('native', ''),
304 params
=JniParams
.Parse(match
.group('params')))
309 def GetStaticCastForReturnType(return_type
):
310 type_map
= { 'String' : 'jstring',
311 'java/lang/String' : 'jstring',
312 'boolean[]': 'jbooleanArray',
313 'byte[]': 'jbyteArray',
314 'char[]': 'jcharArray',
315 'short[]': 'jshortArray',
316 'int[]': 'jintArray',
317 'long[]': 'jlongArray',
318 'double[]': 'jdoubleArray' }
319 ret
= type_map
.get(return_type
, None)
322 if return_type
.endswith('[]'):
323 return 'jobjectArray'
327 def GetEnvCall(is_constructor
, is_static
, return_type
):
328 """Maps the types availabe via env->Call__Method."""
331 env_call_map
= {'boolean': 'Boolean',
342 call
= env_call_map
.get(return_type
, 'Object')
344 call
= 'Static' + call
345 return 'Call' + call
+ 'Method'
348 def GetMangledParam(datatype
):
349 """Returns a mangled identifier for the datatype."""
350 if len(datatype
) <= 2:
351 return datatype
.replace('[', 'A')
353 for i
in range(1, len(datatype
)):
357 elif c
.isupper() or datatype
[i
- 1] in ['/', 'L']:
362 def GetMangledMethodName(name
, params
, return_type
):
363 """Returns a mangled method name for the given signature.
365 The returned name can be used as a C identifier and will be unique for all
366 valid overloads of the same method.
370 params: list of Param.
377 for datatype
in [return_type
] + [x
.datatype
for x
in params
]:
378 mangled_items
+= [GetMangledParam(JniParams
.JavaToJni(datatype
))]
379 mangled_name
= name
+ '_'.join(mangled_items
)
380 assert re
.match(r
'[0-9a-zA-Z_]+', mangled_name
)
384 def MangleCalledByNatives(called_by_natives
):
385 """Mangles all the overloads from the call_by_natives list."""
386 method_counts
= collections
.defaultdict(
387 lambda: collections
.defaultdict(lambda: 0))
388 for called_by_native
in called_by_natives
:
389 java_class_name
= called_by_native
.java_class_name
390 name
= called_by_native
.name
391 method_counts
[java_class_name
][name
] += 1
392 for called_by_native
in called_by_natives
:
393 java_class_name
= called_by_native
.java_class_name
394 method_name
= called_by_native
.name
395 method_id_var_name
= method_name
396 if method_counts
[java_class_name
][method_name
] > 1:
397 method_id_var_name
= GetMangledMethodName(method_name
,
398 called_by_native
.params
,
399 called_by_native
.return_type
)
400 called_by_native
.method_id_var_name
= method_id_var_name
401 return called_by_natives
404 # Regex to match the JNI return types that should be included in a
405 # ScopedJavaLocalRef.
406 RE_SCOPED_JNI_RETURN_TYPES
= re
.compile('jobject|jclass|jstring|.*Array')
408 # Regex to match a string like "@CalledByNative public void foo(int bar)".
409 RE_CALLED_BY_NATIVE
= re
.compile(
410 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
411 '\s+(?P<prefix>[\w ]*?)'
412 '\s*(?P<return_type>\S+?)'
414 '\s*\((?P<params>[^\)]*)\)')
417 def ExtractCalledByNatives(contents
):
418 """Parses all methods annotated with @CalledByNative.
421 contents: the contents of the java file.
424 A list of dict with information about the annotated methods.
425 TODO(bulach): return a CalledByNative object.
428 ParseError: if unable to parse.
430 called_by_natives
= []
431 for match
in re
.finditer(RE_CALLED_BY_NATIVE
, contents
):
432 called_by_natives
+= [CalledByNative(
434 unchecked
='Unchecked' in match
.group('Unchecked'),
435 static
='static' in match
.group('prefix'),
436 java_class_name
=match
.group('annotation') or '',
437 return_type
=match
.group('return_type'),
438 name
=match
.group('name'),
439 params
=JniParams
.Parse(match
.group('params')))]
440 # Check for any @CalledByNative occurrences that weren't matched.
441 unmatched_lines
= re
.sub(RE_CALLED_BY_NATIVE
, '', contents
).split('\n')
442 for line1
, line2
in zip(unmatched_lines
, unmatched_lines
[1:]):
443 if '@CalledByNative' in line1
:
444 raise ParseError('could not parse @CalledByNative method signature',
446 return MangleCalledByNatives(called_by_natives
)
449 class JNIFromJavaP(object):
450 """Uses 'javap' to parse a .class file and generate the JNI header file."""
452 def __init__(self
, contents
, namespace
):
453 self
.contents
= contents
454 self
.namespace
= namespace
455 self
.fully_qualified_class
= re
.match(
456 '.*?(class|interface) (?P<class_name>.*?)( |{)',
457 contents
[1]).group('class_name')
458 self
.fully_qualified_class
= self
.fully_qualified_class
.replace('.', '/')
459 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
460 # away the <...> and use the raw class name that Java 6 would've given us.
461 self
.fully_qualified_class
= self
.fully_qualified_class
.split('<', 1)[0]
462 JniParams
.SetFullyQualifiedClass(self
.fully_qualified_class
)
463 self
.java_class_name
= self
.fully_qualified_class
.split('/')[-1]
464 if not self
.namespace
:
465 self
.namespace
= 'JNI_' + self
.java_class_name
466 re_method
= re
.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
467 '\((?P<params>.*?)\)')
468 self
.called_by_natives
= []
469 for content
in contents
[2:]:
470 match
= re
.match(re_method
, content
)
473 self
.called_by_natives
+= [CalledByNative(
476 static
='static' in match
.group('prefix'),
478 return_type
=match
.group('return_type').replace('.', '/'),
479 name
=match
.group('name'),
480 params
=JniParams
.Parse(match
.group('params').replace('.', '/')))]
481 re_constructor
= re
.compile('.*? public ' +
482 self
.fully_qualified_class
.replace('/', '.') +
483 '\((?P<params>.*?)\)')
484 for content
in contents
[2:]:
485 match
= re
.match(re_constructor
, content
)
488 self
.called_by_natives
+= [CalledByNative(
493 return_type
=self
.fully_qualified_class
,
495 params
=JniParams
.Parse(match
.group('params').replace('.', '/')),
496 is_constructor
=True)]
497 self
.called_by_natives
= MangleCalledByNatives(self
.called_by_natives
)
498 self
.inl_header_file_generator
= InlHeaderFileGenerator(
499 self
.namespace
, self
.fully_qualified_class
, [], self
.called_by_natives
)
501 def GetContent(self
):
502 return self
.inl_header_file_generator
.GetContent()
505 def CreateFromClass(class_file
, namespace
):
506 class_name
= os
.path
.splitext(os
.path
.basename(class_file
))[0]
507 p
= subprocess
.Popen(args
=['javap', class_name
],
508 cwd
=os
.path
.dirname(class_file
),
509 stdout
=subprocess
.PIPE
,
510 stderr
=subprocess
.PIPE
)
511 stdout
, _
= p
.communicate()
512 jni_from_javap
= JNIFromJavaP(stdout
.split('\n'), namespace
)
513 return jni_from_javap
516 class JNIFromJavaSource(object):
517 """Uses the given java source file to generate the JNI header file."""
519 def __init__(self
, contents
, fully_qualified_class
):
520 contents
= self
._RemoveComments
(contents
)
521 JniParams
.SetFullyQualifiedClass(fully_qualified_class
)
522 JniParams
.ExtractImportsAndInnerClasses(contents
)
523 jni_namespace
= ExtractJNINamespace(contents
)
524 natives
= ExtractNatives(contents
)
525 called_by_natives
= ExtractCalledByNatives(contents
)
526 if len(natives
) == 0 and len(called_by_natives
) == 0:
527 raise SyntaxError('Unable to find any JNI methods for %s.' %
528 fully_qualified_class
)
529 inl_header_file_generator
= InlHeaderFileGenerator(
530 jni_namespace
, fully_qualified_class
, natives
, called_by_natives
)
531 self
.content
= inl_header_file_generator
.GetContent()
533 def _RemoveComments(self
, contents
):
534 # We need to support both inline and block comments, and we need to handle
535 # strings that contain '//' or '/*'. Rather than trying to do all that with
536 # regexps, we just pipe the contents through the C preprocessor. We tell cpp
537 # the file has already been preprocessed, so it just removes comments and
538 # doesn't try to parse #include, #pragma etc.
540 # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java
541 # parser. Maybe we could ditch JNIFromJavaSource and just always use
542 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
543 # http://code.google.com/p/chromium/issues/detail?id=138941
544 p
= subprocess
.Popen(args
=['cpp', '-fpreprocessed'],
545 stdin
=subprocess
.PIPE
,
546 stdout
=subprocess
.PIPE
,
547 stderr
=subprocess
.PIPE
)
548 stdout
, _
= p
.communicate(contents
)
551 def GetContent(self
):
555 def CreateFromFile(java_file_name
):
556 contents
= file(java_file_name
).read()
557 fully_qualified_class
= ExtractFullyQualifiedJavaClassName(java_file_name
,
559 return JNIFromJavaSource(contents
, fully_qualified_class
)
562 class InlHeaderFileGenerator(object):
563 """Generates an inline header file for JNI integration."""
565 def __init__(self
, namespace
, fully_qualified_class
, natives
,
567 self
.namespace
= namespace
568 self
.fully_qualified_class
= fully_qualified_class
569 self
.class_name
= self
.fully_qualified_class
.split('/')[-1]
570 self
.natives
= natives
571 self
.called_by_natives
= called_by_natives
572 self
.header_guard
= fully_qualified_class
.replace('/', '_') + '_JNI'
574 def GetContent(self
):
575 """Returns the content of the JNI binding file."""
576 template
= Template("""\
577 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
578 // Use of this source code is governed by a BSD-style license that can be
579 // found in the LICENSE file.
582 // This file is autogenerated by
585 // ${FULLY_QUALIFIED_CLASS}
587 #ifndef ${HEADER_GUARD}
588 #define ${HEADER_GUARD}
592 #include "base/android/jni_android.h"
593 #include "base/android/scoped_java_ref.h"
594 #include "base/basictypes.h"
595 #include "base/logging.h"
597 using base::android::ScopedJavaLocalRef;
599 // Step 1: forward declarations.
601 $CLASS_PATH_DEFINITIONS
605 $FORWARD_DECLARATIONS
607 // Step 2: method stubs.
610 // Step 3: RegisterNatives.
612 static bool RegisterNativesImpl(JNIEnv* env) {
613 $REGISTER_NATIVES_IMPL
617 #endif // ${HEADER_GUARD}
619 script_components
= os
.path
.abspath(sys
.argv
[0]).split(os
.path
.sep
)
620 base_index
= script_components
.index('base')
621 script_name
= os
.sep
.join(script_components
[base_index
:])
623 'SCRIPT_NAME': script_name
,
624 'FULLY_QUALIFIED_CLASS': self
.fully_qualified_class
,
625 'CLASS_PATH_DEFINITIONS': self
.GetClassPathDefinitionsString(),
626 'FORWARD_DECLARATIONS': self
.GetForwardDeclarationsString(),
627 'METHOD_STUBS': self
.GetMethodStubsString(),
628 'OPEN_NAMESPACE': self
.GetOpenNamespaceString(),
629 'REGISTER_NATIVES_IMPL': self
.GetRegisterNativesImplString(),
630 'CLOSE_NAMESPACE': self
.GetCloseNamespaceString(),
631 'HEADER_GUARD': self
.header_guard
,
633 return WrapOutput(template
.substitute(values
))
635 def GetClassPathDefinitionsString(self
):
637 ret
+= [self
.GetClassPathDefinitions()]
638 return '\n'.join(ret
)
640 def GetForwardDeclarationsString(self
):
642 for native
in self
.natives
:
643 if native
.type != 'method':
644 ret
+= [self
.GetForwardDeclaration(native
)]
645 return '\n'.join(ret
)
647 def GetMethodStubsString(self
):
649 for native
in self
.natives
:
650 if native
.type == 'method':
651 ret
+= [self
.GetNativeMethodStub(native
)]
652 for called_by_native
in self
.called_by_natives
:
653 ret
+= [self
.GetCalledByNativeMethodStub(called_by_native
)]
654 return '\n'.join(ret
)
656 def GetKMethodsString(self
, clazz
):
658 for native
in self
.natives
:
659 if (native
.java_class_name
== clazz
or
660 (not native
.java_class_name
and clazz
== self
.class_name
)):
661 ret
+= [self
.GetKMethodArrayEntry(native
)]
662 return '\n'.join(ret
)
664 def GetRegisterNativesImplString(self
):
665 """Returns the implementation for RegisterNatives."""
666 template
= Template("""\
667 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
670 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
672 if (env->RegisterNatives(g_${JAVA_CLASS}_clazz,
673 kMethods${JAVA_CLASS},
674 kMethods${JAVA_CLASS}Size) < 0) {
675 LOG(ERROR) << "RegisterNatives failed in " << __FILE__;
679 ret
= [self
.GetFindClasses()]
680 all_classes
= self
.GetUniqueClasses(self
.natives
)
681 all_classes
[self
.class_name
] = self
.fully_qualified_class
682 for clazz
in all_classes
:
683 kmethods
= self
.GetKMethodsString(clazz
)
685 values
= {'JAVA_CLASS': clazz
,
686 'KMETHODS': kmethods
}
687 ret
+= [template
.substitute(values
)]
688 if not ret
: return ''
689 return '\n' + '\n'.join(ret
)
691 def GetOpenNamespaceString(self
):
693 all_namespaces
= ['namespace %s {' % ns
694 for ns
in self
.namespace
.split('::')]
695 return '\n'.join(all_namespaces
)
698 def GetCloseNamespaceString(self
):
700 all_namespaces
= ['} // namespace %s' % ns
701 for ns
in self
.namespace
.split('::')]
702 all_namespaces
.reverse()
703 return '\n'.join(all_namespaces
) + '\n'
706 def GetJNIFirstParam(self
, native
):
708 if native
.type == 'method':
709 ret
= ['jobject obj']
710 elif native
.type == 'function':
712 ret
= ['jclass clazz']
714 ret
= ['jobject obj']
717 def GetParamsInDeclaration(self
, native
):
718 """Returns the params for the stub declaration.
721 native: the native dictionary describing the method.
724 A string containing the params.
726 return ',\n '.join(self
.GetJNIFirstParam(native
) +
727 [JavaDataTypeToC(param
.datatype
) + ' ' +
729 for param
in native
.params
])
731 def GetCalledByNativeParamsInDeclaration(self
, called_by_native
):
732 return ',\n '.join([JavaDataTypeToC(param
.datatype
) + ' ' +
734 for param
in called_by_native
.params
])
736 def GetForwardDeclaration(self
, native
):
737 template
= Template("""
738 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
740 values
= {'RETURN': JavaDataTypeToC(native
.return_type
),
742 'PARAMS': self
.GetParamsInDeclaration(native
)}
743 return template
.substitute(values
)
745 def GetNativeMethodStub(self
, native
):
746 """Returns stubs for native methods."""
747 template
= Template("""\
748 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {
749 DCHECK(${PARAM0_NAME}) << "${NAME}";
750 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
751 return native->${NAME}(env, obj${PARAMS_IN_CALL})${POST_CALL};
754 params_for_call
= ', '.join(p
.name
for p
in native
.params
[1:])
756 params_for_call
= ', ' + params_for_call
758 return_type
= JavaDataTypeToC(native
.return_type
)
759 if re
.match(RE_SCOPED_JNI_RETURN_TYPES
, return_type
):
760 scoped_return_type
= 'ScopedJavaLocalRef<' + return_type
+ '>'
761 post_call
= '.Release()'
763 scoped_return_type
= return_type
766 'RETURN': return_type
,
767 'SCOPED_RETURN': scoped_return_type
,
769 'PARAMS_IN_DECLARATION': self
.GetParamsInDeclaration(native
),
770 'PARAM0_NAME': native
.params
[0].name
,
771 'P0_TYPE': native
.p0_type
,
772 'PARAMS_IN_CALL': params_for_call
,
773 'POST_CALL': post_call
775 return template
.substitute(values
)
777 def GetCalledByNativeMethodStub(self
, called_by_native
):
778 """Returns a string."""
779 function_signature_template
= Template("""\
780 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
781 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
782 function_header_template
= Template("""\
783 ${FUNCTION_SIGNATURE} {""")
784 function_header_with_unused_template
= Template("""\
785 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
786 ${FUNCTION_SIGNATURE} {""")
787 template
= Template("""
788 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
790 /* Must call RegisterNativesImpl() */
791 DCHECK(g_${JAVA_CLASS}_clazz);
792 jmethodID method_id =
793 ${GET_METHOD_ID_IMPL}
794 ${RETURN_DECLARATION}
795 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
796 method_id${PARAMS_IN_CALL})${POST_CALL};
800 if called_by_native
.static
or called_by_native
.is_constructor
:
801 first_param_in_declaration
= ''
802 first_param_in_call
= ('g_%s_clazz' %
803 (called_by_native
.java_class_name
or
806 first_param_in_declaration
= ', jobject obj'
807 first_param_in_call
= 'obj'
808 params_in_declaration
= self
.GetCalledByNativeParamsInDeclaration(
810 if params_in_declaration
:
811 params_in_declaration
= ', ' + params_in_declaration
812 params_for_call
= ', '.join(param
.name
813 for param
in called_by_native
.params
)
815 params_for_call
= ', ' + params_for_call
818 if called_by_native
.static_cast
:
819 pre_call
= 'static_cast<%s>(' % called_by_native
.static_cast
822 if not called_by_native
.unchecked
:
823 check_exception
= 'base::android::CheckException(env);'
824 return_type
= JavaDataTypeToC(called_by_native
.return_type
)
825 return_declaration
= ''
827 if return_type
!= 'void':
828 pre_call
= ' ' + pre_call
829 return_declaration
= return_type
+ ' ret ='
830 if re
.match(RE_SCOPED_JNI_RETURN_TYPES
, return_type
):
831 return_type
= 'ScopedJavaLocalRef<' + return_type
+ '>'
832 return_clause
= 'return ' + return_type
+ '(env, ret);'
834 return_clause
= 'return ret;'
836 'JAVA_CLASS': called_by_native
.java_class_name
or self
.class_name
,
837 'METHOD': called_by_native
.name
,
838 'RETURN_TYPE': return_type
,
839 'RETURN_DECLARATION': return_declaration
,
840 'RETURN_CLAUSE': return_clause
,
841 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration
,
842 'PARAMS_IN_DECLARATION': params_in_declaration
,
843 'STATIC': 'Static' if called_by_native
.static
else '',
844 'PRE_CALL': pre_call
,
845 'POST_CALL': post_call
,
846 'ENV_CALL': called_by_native
.env_call
,
847 'FIRST_PARAM_IN_CALL': first_param_in_call
,
848 'PARAMS_IN_CALL': params_for_call
,
849 'METHOD_ID_VAR_NAME': called_by_native
.method_id_var_name
,
850 'CHECK_EXCEPTION': check_exception
,
851 'GET_METHOD_ID_IMPL': self
.GetMethodIDImpl(called_by_native
)
853 values
['FUNCTION_SIGNATURE'] = (
854 function_signature_template
.substitute(values
))
855 if called_by_native
.system_class
:
856 values
['FUNCTION_HEADER'] = (
857 function_header_with_unused_template
.substitute(values
))
859 values
['FUNCTION_HEADER'] = function_header_template
.substitute(values
)
860 return template
.substitute(values
)
862 def GetKMethodArrayEntry(self
, native
):
863 template
= Template("""\
864 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""")
865 values
= {'NAME': native
.name
,
866 'JNI_SIGNATURE': JniParams
.Signature(native
.params
,
869 return template
.substitute(values
)
871 def GetUniqueClasses(self
, origin
):
872 ret
= {self
.class_name
: self
.fully_qualified_class
}
874 class_name
= self
.class_name
875 jni_class_path
= self
.fully_qualified_class
876 if entry
.java_class_name
:
877 class_name
= entry
.java_class_name
878 jni_class_path
= self
.fully_qualified_class
+ '$' + class_name
879 ret
[class_name
] = jni_class_path
882 def GetClassPathDefinitions(self
):
883 """Returns the ClassPath constants."""
885 template
= Template("""\
886 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
887 native_classes
= self
.GetUniqueClasses(self
.natives
)
888 called_by_native_classes
= self
.GetUniqueClasses(self
.called_by_natives
)
889 all_classes
= native_classes
890 all_classes
.update(called_by_native_classes
)
891 for clazz
in all_classes
:
894 'JNI_CLASS_PATH': JniParams
.RemapClassName(all_classes
[clazz
]),
896 ret
+= [template
.substitute(values
)]
898 for clazz
in called_by_native_classes
:
899 template
= Template("""\
900 // Leaking this jclass as we cannot use LazyInstance from some threads.
901 jclass g_${JAVA_CLASS}_clazz = NULL;""")
905 ret
+= [template
.substitute(values
)]
906 return '\n'.join(ret
)
908 def GetFindClasses(self
):
909 """Returns the imlementation of FindClass for all known classes."""
910 template
= Template("""\
911 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
912 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
914 for clazz
in self
.GetUniqueClasses(self
.called_by_natives
):
915 values
= {'JAVA_CLASS': clazz
}
916 ret
+= [template
.substitute(values
)]
917 return '\n'.join(ret
)
919 def GetMethodIDImpl(self
, called_by_native
):
920 """Returns the implementation of GetMethodID."""
921 template
= Template("""\
922 base::android::MethodID::LazyGet<
923 base::android::MethodID::TYPE_${STATIC}>(
924 env, g_${JAVA_CLASS}_clazz,
927 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
929 jni_name
= called_by_native
.name
930 jni_return_type
= called_by_native
.return_type
931 if called_by_native
.is_constructor
:
933 jni_return_type
= 'void'
935 'JAVA_CLASS': called_by_native
.java_class_name
or self
.class_name
,
936 'JNI_NAME': jni_name
,
937 'METHOD_ID_VAR_NAME': called_by_native
.method_id_var_name
,
938 'STATIC': 'STATIC' if called_by_native
.static
else 'INSTANCE',
939 'JNI_SIGNATURE': JniParams
.Signature(called_by_native
.params
,
943 return template
.substitute(values
)
946 def WrapOutput(output
):
948 for line
in output
.splitlines():
949 # Do not wrap lines under 80 characters or preprocessor directives.
950 if len(line
) < 80 or line
.lstrip()[:1] == '#':
951 stripped
= line
.rstrip()
952 if len(ret
) == 0 or len(ret
[-1]) or len(stripped
):
955 first_line_indent
= ' ' * (len(line
) - len(line
.lstrip()))
956 subsequent_indent
= first_line_indent
+ ' ' * 4
957 if line
.startswith('//'):
958 subsequent_indent
= '//' + subsequent_indent
959 wrapper
= textwrap
.TextWrapper(width
=80,
960 subsequent_indent
=subsequent_indent
,
961 break_long_words
=False)
962 ret
+= [wrapped
.rstrip() for wrapped
in wrapper
.wrap(line
)]
964 return '\n'.join(ret
)
967 def ExtractJarInputFile(jar_file
, input_file
, out_dir
):
968 """Extracts input file from jar and returns the filename.
970 The input file is extracted to the same directory that the generated jni
971 headers will be placed in. This is passed as an argument to script.
974 jar_file: the jar file containing the input files to extract.
975 input_files: the list of files to extract from the jar file.
976 out_dir: the name of the directories to extract to.
979 the name of extracted input file.
981 jar_file
= zipfile
.ZipFile(jar_file
)
983 out_dir
= os
.path
.join(out_dir
, os
.path
.dirname(input_file
))
987 if e
.errno
!= errno
.EEXIST
:
989 extracted_file_name
= os
.path
.join(out_dir
, os
.path
.basename(input_file
))
990 with
open(extracted_file_name
, 'w') as outfile
:
991 outfile
.write(jar_file
.read(input_file
))
993 return extracted_file_name
996 def GenerateJNIHeader(input_file
, output_file
, namespace
, skip_if_same
):
998 if os
.path
.splitext(input_file
)[1] == '.class':
999 jni_from_javap
= JNIFromJavaP
.CreateFromClass(input_file
, namespace
)
1000 content
= jni_from_javap
.GetContent()
1002 jni_from_java_source
= JNIFromJavaSource
.CreateFromFile(input_file
)
1003 content
= jni_from_java_source
.GetContent()
1004 except ParseError
, e
:
1008 if not os
.path
.exists(os
.path
.dirname(os
.path
.abspath(output_file
))):
1009 os
.makedirs(os
.path
.dirname(os
.path
.abspath(output_file
)))
1010 if skip_if_same
and os
.path
.exists(output_file
):
1011 with
file(output_file
, 'r') as f
:
1012 existing_content
= f
.read()
1013 if existing_content
== content
:
1015 with
file(output_file
, 'w') as f
:
1022 usage
= """usage: %prog [OPTIONS]
1023 This script will parse the given java source code extracting the native
1024 declarations and print the header file to stdout (or a file).
1025 See SampleForTests.java for more details.
1027 option_parser
= optparse
.OptionParser(usage
=usage
)
1028 option_parser
.add_option('-j', dest
='jar_file',
1029 help='Extract the list of input files from'
1030 ' a specified jar file.'
1031 ' Uses javap to extract the methods from a'
1032 ' pre-compiled class. --input should point'
1033 ' to pre-compiled Java .class files.')
1034 option_parser
.add_option('-n', dest
='namespace',
1035 help='Uses as a namespace in the generated header,'
1036 ' instead of the javap class name.')
1037 option_parser
.add_option('--input_file',
1038 help='Single input file name. The output file name '
1039 'will be derived from it. Must be used with '
1041 option_parser
.add_option('--output_dir',
1042 help='The output directory. Must be used with '
1044 option_parser
.add_option('--optimize_generation', type="int",
1045 default
=0, help='Whether we should optimize JNI '
1046 'generation by not regenerating files if they have '
1048 option_parser
.add_option('--jarjar',
1049 help='Path to optional jarjar rules file.')
1050 options
, args
= option_parser
.parse_args(argv
)
1051 if options
.jar_file
:
1052 input_file
= ExtractJarInputFile(options
.jar_file
, options
.input_file
,
1055 input_file
= options
.input_file
1057 if options
.output_dir
:
1058 root_name
= os
.path
.splitext(os
.path
.basename(input_file
))[0]
1059 output_file
= os
.path
.join(options
.output_dir
, root_name
) + '_jni.h'
1061 with
open(options
.jarjar
) as f
:
1062 JniParams
.SetJarJarMappings(f
.read())
1063 GenerateJNIHeader(input_file
, output_file
, options
.namespace
,
1064 options
.optimize_generation
)
1067 if __name__
== '__main__':
1068 sys
.exit(main(sys
.argv
))