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
= ''
124 def SetFullyQualifiedClass(fully_qualified_class
):
125 JniParams
._fully
_qualified
_class
= 'L' + fully_qualified_class
126 JniParams
._package
= '/'.join(fully_qualified_class
.split('/')[:-1])
129 def ExtractImportsAndInnerClasses(contents
):
130 contents
= contents
.replace('\n', '')
131 re_import
= re
.compile(r
'import.*?(?P<class>\S*?);')
132 for match
in re
.finditer(re_import
, contents
):
133 JniParams
._imports
+= ['L' + match
.group('class').replace('.', '/')]
135 re_inner
= re
.compile(r
'(class|interface)\s+?(?P<name>\w+?)\W')
136 for match
in re
.finditer(re_inner
, contents
):
137 inner
= match
.group('name')
138 if not JniParams
._fully
_qualified
_class
.endswith(inner
):
139 JniParams
._inner
_classes
+= [JniParams
._fully
_qualified
_class
+ '$' +
143 def JavaToJni(param
):
144 """Converts a java param into a JNI signature type."""
156 object_param_list
= [
157 'Ljava/lang/Boolean',
158 'Ljava/lang/Integer',
166 while param
[-2:] == '[]':
171 param
= param
[:param
.index('<')]
172 if param
in pod_param_map
:
173 return prefix
+ pod_param_map
[param
]
175 # Coming from javap, use the fully qualified param directly.
176 return prefix
+ 'L' + param
+ ';'
177 for qualified_name
in (object_param_list
+
178 [JniParams
._fully
_qualified
_class
] +
179 JniParams
._inner
_classes
):
180 if (qualified_name
.endswith('/' + param
) or
181 qualified_name
.endswith('$' + param
.replace('.', '$')) or
182 qualified_name
== 'L' + param
):
183 return prefix
+ qualified_name
+ ';'
185 # Is it from an import? (e.g. referecing Class from import pkg.Class;
186 # note that referencing an inner class Inner from import pkg.Class.Inner
188 for qualified_name
in JniParams
._imports
:
189 if qualified_name
.endswith('/' + param
):
190 # Ensure it's not an inner class.
191 components
= qualified_name
.split('/')
192 if len(components
) > 2 and components
[-2][0].isupper():
193 raise SyntaxError('Inner class (%s) can not be imported '
194 'and used by JNI (%s). Please import the outer '
195 'class and use Outer.Inner instead.' %
196 (qualified_name
, param
))
197 return prefix
+ qualified_name
+ ';'
199 # Is it an inner class from an outer class import? (e.g. referencing
200 # Class.Inner from import pkg.Class).
202 components
= param
.split('.')
203 outer
= '/'.join(components
[:-1])
204 inner
= components
[-1]
205 for qualified_name
in JniParams
._imports
:
206 if qualified_name
.endswith('/' + outer
):
207 return prefix
+ qualified_name
+ '$' + inner
+ ';'
209 # Type not found, falling back to same package as this class.
210 return prefix
+ 'L' + JniParams
._package
+ '/' + param
+ ';'
213 def Signature(params
, returns
, wrap
):
214 """Returns the JNI signature for the given datatypes."""
216 items
+= [JniParams
.JavaToJni(param
.datatype
) for param
in params
]
218 items
+= [JniParams
.JavaToJni(returns
)]
220 return '\n' + '\n'.join(['"' + item
+ '"' for item
in items
])
222 return '"' + ''.join(items
) + '"'
226 """Parses the params into a list of Param objects."""
230 for p
in [p
.strip() for p
in params
.split(',')]:
233 items
.remove('final')
236 name
=(items
[1] if len(items
) > 1 else 'p%s' % len(ret
)),
242 def ExtractJNINamespace(contents
):
243 re_jni_namespace
= re
.compile('.*?@JNINamespace\("(.*?)"\)')
244 m
= re
.findall(re_jni_namespace
, contents
)
250 def ExtractFullyQualifiedJavaClassName(java_file_name
, contents
):
251 re_package
= re
.compile('.*?package (.*?);')
252 matches
= re
.findall(re_package
, contents
)
254 raise SyntaxError('Unable to find "package" line in %s' % java_file_name
)
255 return (matches
[0].replace('.', '/') + '/' +
256 os
.path
.splitext(os
.path
.basename(java_file_name
))[0])
259 def ExtractNatives(contents
):
260 """Returns a list of dict containing information about a native method."""
261 contents
= contents
.replace('\n', '')
263 re_native
= re
.compile(r
'(@NativeClassQualifiedName'
264 '\(\"(?P<native_class_name>.*?)\"\))?\s*'
265 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\)))?\s*'
266 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*?native '
267 '(?P<return_type>\S*?) '
268 '(?P<name>\w+?)\((?P<params>.*?)\);')
269 for match
in re
.finditer(re_native
, contents
):
270 native
= NativeMethod(
271 static
='static' in match
.group('qualifiers'),
272 java_class_name
=match
.group('java_class_name'),
273 native_class_name
=match
.group('native_class_name'),
274 return_type
=match
.group('return_type'),
275 name
=match
.group('name').replace('native', ''),
276 params
=JniParams
.Parse(match
.group('params')))
281 def GetStaticCastForReturnType(return_type
):
282 type_map
= { 'String' : 'jstring',
283 'java/lang/String' : 'jstring',
284 'boolean[]': 'jbooleanArray',
285 'byte[]': 'jbyteArray',
286 'char[]': 'jcharArray',
287 'short[]': 'jshortArray',
288 'int[]': 'jintArray',
289 'long[]': 'jlongArray',
290 'double[]': 'jdoubleArray' }
291 ret
= type_map
.get(return_type
, None)
294 if return_type
.endswith('[]'):
295 return 'jobjectArray'
299 def GetEnvCall(is_constructor
, is_static
, return_type
):
300 """Maps the types availabe via env->Call__Method."""
303 env_call_map
= {'boolean': 'Boolean',
314 call
= env_call_map
.get(return_type
, 'Object')
316 call
= 'Static' + call
317 return 'Call' + call
+ 'Method'
320 def GetMangledParam(datatype
):
321 """Returns a mangled identifier for the datatype."""
322 if len(datatype
) <= 2:
323 return datatype
.replace('[', 'A')
325 for i
in range(1, len(datatype
)):
329 elif c
.isupper() or datatype
[i
- 1] in ['/', 'L']:
334 def GetMangledMethodName(name
, params
, return_type
):
335 """Returns a mangled method name for the given signature.
337 The returned name can be used as a C identifier and will be unique for all
338 valid overloads of the same method.
342 params: list of Param.
349 for datatype
in [return_type
] + [x
.datatype
for x
in params
]:
350 mangled_items
+= [GetMangledParam(JniParams
.JavaToJni(datatype
))]
351 mangled_name
= name
+ '_'.join(mangled_items
)
352 assert re
.match(r
'[0-9a-zA-Z_]+', mangled_name
)
356 def MangleCalledByNatives(called_by_natives
):
357 """Mangles all the overloads from the call_by_natives list."""
358 method_counts
= collections
.defaultdict(
359 lambda: collections
.defaultdict(lambda: 0))
360 for called_by_native
in called_by_natives
:
361 java_class_name
= called_by_native
.java_class_name
362 name
= called_by_native
.name
363 method_counts
[java_class_name
][name
] += 1
364 for called_by_native
in called_by_natives
:
365 java_class_name
= called_by_native
.java_class_name
366 method_name
= called_by_native
.name
367 method_id_var_name
= method_name
368 if method_counts
[java_class_name
][method_name
] > 1:
369 method_id_var_name
= GetMangledMethodName(method_name
,
370 called_by_native
.params
,
371 called_by_native
.return_type
)
372 called_by_native
.method_id_var_name
= method_id_var_name
373 return called_by_natives
376 # Regex to match the JNI return types that should be included in a
377 # ScopedJavaLocalRef.
378 RE_SCOPED_JNI_RETURN_TYPES
= re
.compile('jobject|jclass|jstring|.*Array')
380 # Regex to match a string like "@CalledByNative public void foo(int bar)".
381 RE_CALLED_BY_NATIVE
= re
.compile(
382 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
383 '\s+(?P<prefix>[\w ]*?)'
384 '\s*(?P<return_type>\S+?)'
386 '\s*\((?P<params>[^\)]*)\)')
389 def ExtractCalledByNatives(contents
):
390 """Parses all methods annotated with @CalledByNative.
393 contents: the contents of the java file.
396 A list of dict with information about the annotated methods.
397 TODO(bulach): return a CalledByNative object.
400 ParseError: if unable to parse.
402 called_by_natives
= []
403 for match
in re
.finditer(RE_CALLED_BY_NATIVE
, contents
):
404 called_by_natives
+= [CalledByNative(
406 unchecked
='Unchecked' in match
.group('Unchecked'),
407 static
='static' in match
.group('prefix'),
408 java_class_name
=match
.group('annotation') or '',
409 return_type
=match
.group('return_type'),
410 name
=match
.group('name'),
411 params
=JniParams
.Parse(match
.group('params')))]
412 # Check for any @CalledByNative occurrences that weren't matched.
413 unmatched_lines
= re
.sub(RE_CALLED_BY_NATIVE
, '', contents
).split('\n')
414 for line1
, line2
in zip(unmatched_lines
, unmatched_lines
[1:]):
415 if '@CalledByNative' in line1
:
416 raise ParseError('could not parse @CalledByNative method signature',
418 return MangleCalledByNatives(called_by_natives
)
421 class JNIFromJavaP(object):
422 """Uses 'javap' to parse a .class file and generate the JNI header file."""
424 def __init__(self
, contents
, namespace
):
425 self
.contents
= contents
426 self
.namespace
= namespace
427 self
.fully_qualified_class
= re
.match(
428 '.*?(class|interface) (?P<class_name>.*?)( |{)',
429 contents
[1]).group('class_name')
430 self
.fully_qualified_class
= self
.fully_qualified_class
.replace('.', '/')
431 JniParams
.SetFullyQualifiedClass(self
.fully_qualified_class
)
432 self
.java_class_name
= self
.fully_qualified_class
.split('/')[-1]
433 if not self
.namespace
:
434 self
.namespace
= 'JNI_' + self
.java_class_name
435 re_method
= re
.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
436 '\((?P<params>.*?)\)')
437 self
.called_by_natives
= []
438 for content
in contents
[2:]:
439 match
= re
.match(re_method
, content
)
442 self
.called_by_natives
+= [CalledByNative(
445 static
='static' in match
.group('prefix'),
447 return_type
=match
.group('return_type').replace('.', '/'),
448 name
=match
.group('name'),
449 params
=JniParams
.Parse(match
.group('params').replace('.', '/')))]
450 re_constructor
= re
.compile('.*? public ' +
451 self
.fully_qualified_class
.replace('/', '.') +
452 '\((?P<params>.*?)\)')
453 for content
in contents
[2:]:
454 match
= re
.match(re_constructor
, content
)
457 self
.called_by_natives
+= [CalledByNative(
462 return_type
=self
.fully_qualified_class
,
464 params
=JniParams
.Parse(match
.group('params').replace('.', '/')),
465 is_constructor
=True)]
466 self
.called_by_natives
= MangleCalledByNatives(self
.called_by_natives
)
467 self
.inl_header_file_generator
= InlHeaderFileGenerator(
468 self
.namespace
, self
.fully_qualified_class
, [], self
.called_by_natives
)
470 def GetContent(self
):
471 return self
.inl_header_file_generator
.GetContent()
474 def CreateFromClass(class_file
, namespace
):
475 class_name
= os
.path
.splitext(os
.path
.basename(class_file
))[0]
476 p
= subprocess
.Popen(args
=['javap', class_name
],
477 cwd
=os
.path
.dirname(class_file
),
478 stdout
=subprocess
.PIPE
,
479 stderr
=subprocess
.PIPE
)
480 stdout
, _
= p
.communicate()
481 jni_from_javap
= JNIFromJavaP(stdout
.split('\n'), namespace
)
482 return jni_from_javap
485 class JNIFromJavaSource(object):
486 """Uses the given java source file to generate the JNI header file."""
488 def __init__(self
, contents
, fully_qualified_class
):
489 contents
= self
._RemoveComments
(contents
)
490 JniParams
.SetFullyQualifiedClass(fully_qualified_class
)
491 JniParams
.ExtractImportsAndInnerClasses(contents
)
492 jni_namespace
= ExtractJNINamespace(contents
)
493 natives
= ExtractNatives(contents
)
494 called_by_natives
= ExtractCalledByNatives(contents
)
495 if len(natives
) == 0 and len(called_by_natives
) == 0:
496 raise SyntaxError('Unable to find any JNI methods for %s.' %
497 fully_qualified_class
)
498 inl_header_file_generator
= InlHeaderFileGenerator(
499 jni_namespace
, fully_qualified_class
, natives
, called_by_natives
)
500 self
.content
= inl_header_file_generator
.GetContent()
502 def _RemoveComments(self
, contents
):
503 # We need to support both inline and block comments, and we need to handle
504 # strings that contain '//' or '/*'. Rather than trying to do all that with
505 # regexps, we just pipe the contents through the C preprocessor. We tell cpp
506 # the file has already been preprocessed, so it just removes comments and
507 # doesn't try to parse #include, #pragma etc.
509 # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java
510 # parser. Maybe we could ditch JNIFromJavaSource and just always use
511 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
512 # http://code.google.com/p/chromium/issues/detail?id=138941
513 p
= subprocess
.Popen(args
=['cpp', '-fpreprocessed'],
514 stdin
=subprocess
.PIPE
,
515 stdout
=subprocess
.PIPE
,
516 stderr
=subprocess
.PIPE
)
517 stdout
, _
= p
.communicate(contents
)
520 def GetContent(self
):
524 def CreateFromFile(java_file_name
):
525 contents
= file(java_file_name
).read()
526 fully_qualified_class
= ExtractFullyQualifiedJavaClassName(java_file_name
,
528 return JNIFromJavaSource(contents
, fully_qualified_class
)
531 class InlHeaderFileGenerator(object):
532 """Generates an inline header file for JNI integration."""
534 def __init__(self
, namespace
, fully_qualified_class
, natives
,
536 self
.namespace
= namespace
537 self
.fully_qualified_class
= fully_qualified_class
538 self
.class_name
= self
.fully_qualified_class
.split('/')[-1]
539 self
.natives
= natives
540 self
.called_by_natives
= called_by_natives
541 self
.header_guard
= fully_qualified_class
.replace('/', '_') + '_JNI'
543 def GetContent(self
):
544 """Returns the content of the JNI binding file."""
545 template
= Template("""\
546 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
547 // Use of this source code is governed by a BSD-style license that can be
548 // found in the LICENSE file.
551 // This file is autogenerated by
554 // ${FULLY_QUALIFIED_CLASS}
556 #ifndef ${HEADER_GUARD}
557 #define ${HEADER_GUARD}
561 #include "base/android/jni_android.h"
562 #include "base/android/scoped_java_ref.h"
563 #include "base/basictypes.h"
564 #include "base/logging.h"
566 using base::android::ScopedJavaLocalRef;
568 // Step 1: forward declarations.
570 $CLASS_PATH_DEFINITIONS
574 $FORWARD_DECLARATIONS
576 // Step 2: method stubs.
579 // Step 3: RegisterNatives.
581 static bool RegisterNativesImpl(JNIEnv* env) {
582 $REGISTER_NATIVES_IMPL
586 #endif // ${HEADER_GUARD}
588 script_components
= os
.path
.abspath(sys
.argv
[0]).split(os
.path
.sep
)
589 base_index
= script_components
.index('base')
590 script_name
= os
.sep
.join(script_components
[base_index
:])
592 'SCRIPT_NAME': script_name
,
593 'FULLY_QUALIFIED_CLASS': self
.fully_qualified_class
,
594 'CLASS_PATH_DEFINITIONS': self
.GetClassPathDefinitionsString(),
595 'FORWARD_DECLARATIONS': self
.GetForwardDeclarationsString(),
596 'METHOD_STUBS': self
.GetMethodStubsString(),
597 'OPEN_NAMESPACE': self
.GetOpenNamespaceString(),
598 'REGISTER_NATIVES_IMPL': self
.GetRegisterNativesImplString(),
599 'CLOSE_NAMESPACE': self
.GetCloseNamespaceString(),
600 'HEADER_GUARD': self
.header_guard
,
602 return WrapOutput(template
.substitute(values
))
604 def GetClassPathDefinitionsString(self
):
606 ret
+= [self
.GetClassPathDefinitions()]
607 return '\n'.join(ret
)
609 def GetForwardDeclarationsString(self
):
611 for native
in self
.natives
:
612 if native
.type != 'method':
613 ret
+= [self
.GetForwardDeclaration(native
)]
614 return '\n'.join(ret
)
616 def GetMethodStubsString(self
):
618 for native
in self
.natives
:
619 if native
.type == 'method':
620 ret
+= [self
.GetNativeMethodStub(native
)]
621 for called_by_native
in self
.called_by_natives
:
622 ret
+= [self
.GetCalledByNativeMethodStub(called_by_native
)]
623 return '\n'.join(ret
)
625 def GetKMethodsString(self
, clazz
):
627 for native
in self
.natives
:
628 if (native
.java_class_name
== clazz
or
629 (not native
.java_class_name
and clazz
== self
.class_name
)):
630 ret
+= [self
.GetKMethodArrayEntry(native
)]
631 return '\n'.join(ret
)
633 def GetRegisterNativesImplString(self
):
634 """Returns the implementation for RegisterNatives."""
635 template
= Template("""\
636 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
639 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
641 if (env->RegisterNatives(g_${JAVA_CLASS}_clazz,
642 kMethods${JAVA_CLASS},
643 kMethods${JAVA_CLASS}Size) < 0) {
644 LOG(ERROR) << "RegisterNatives failed in " << __FILE__;
648 ret
= [self
.GetFindClasses()]
649 all_classes
= self
.GetUniqueClasses(self
.natives
)
650 all_classes
[self
.class_name
] = self
.fully_qualified_class
651 for clazz
in all_classes
:
652 kmethods
= self
.GetKMethodsString(clazz
)
654 values
= {'JAVA_CLASS': clazz
,
655 'KMETHODS': kmethods
}
656 ret
+= [template
.substitute(values
)]
657 if not ret
: return ''
658 return '\n' + '\n'.join(ret
)
660 def GetOpenNamespaceString(self
):
662 all_namespaces
= ['namespace %s {' % ns
663 for ns
in self
.namespace
.split('::')]
664 return '\n'.join(all_namespaces
)
667 def GetCloseNamespaceString(self
):
669 all_namespaces
= ['} // namespace %s' % ns
670 for ns
in self
.namespace
.split('::')]
671 all_namespaces
.reverse()
672 return '\n'.join(all_namespaces
) + '\n'
675 def GetJNIFirstParam(self
, native
):
677 if native
.type == 'method':
678 ret
= ['jobject obj']
679 elif native
.type == 'function':
681 ret
= ['jclass clazz']
683 ret
= ['jobject obj']
686 def GetParamsInDeclaration(self
, native
):
687 """Returns the params for the stub declaration.
690 native: the native dictionary describing the method.
693 A string containing the params.
695 return ',\n '.join(self
.GetJNIFirstParam(native
) +
696 [JavaDataTypeToC(param
.datatype
) + ' ' +
698 for param
in native
.params
])
700 def GetCalledByNativeParamsInDeclaration(self
, called_by_native
):
701 return ',\n '.join([JavaDataTypeToC(param
.datatype
) + ' ' +
703 for param
in called_by_native
.params
])
705 def GetForwardDeclaration(self
, native
):
706 template
= Template("""
707 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
709 values
= {'RETURN': JavaDataTypeToC(native
.return_type
),
711 'PARAMS': self
.GetParamsInDeclaration(native
)}
712 return template
.substitute(values
)
714 def GetNativeMethodStub(self
, native
):
715 """Returns stubs for native methods."""
716 template
= Template("""\
717 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {
718 DCHECK(${PARAM0_NAME}) << "${NAME}";
719 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
720 return native->${NAME}(env, obj${PARAMS_IN_CALL})${POST_CALL};
723 params_for_call
= ', '.join(p
.name
for p
in native
.params
[1:])
725 params_for_call
= ', ' + params_for_call
727 return_type
= JavaDataTypeToC(native
.return_type
)
728 if re
.match(RE_SCOPED_JNI_RETURN_TYPES
, return_type
):
729 scoped_return_type
= 'ScopedJavaLocalRef<' + return_type
+ '>'
730 post_call
= '.Release()'
732 scoped_return_type
= return_type
735 'RETURN': return_type
,
736 'SCOPED_RETURN': scoped_return_type
,
738 'PARAMS_IN_DECLARATION': self
.GetParamsInDeclaration(native
),
739 'PARAM0_NAME': native
.params
[0].name
,
740 'P0_TYPE': native
.p0_type
,
741 'PARAMS_IN_CALL': params_for_call
,
742 'POST_CALL': post_call
744 return template
.substitute(values
)
746 def GetCalledByNativeMethodStub(self
, called_by_native
):
747 """Returns a string."""
748 function_signature_template
= Template("""\
749 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
750 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
751 function_header_template
= Template("""\
752 ${FUNCTION_SIGNATURE} {""")
753 function_header_with_unused_template
= Template("""\
754 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
755 ${FUNCTION_SIGNATURE} {""")
756 template
= Template("""
757 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
759 /* Must call RegisterNativesImpl() */
760 DCHECK(g_${JAVA_CLASS}_clazz);
761 jmethodID method_id =
762 ${GET_METHOD_ID_IMPL}
763 ${RETURN_DECLARATION}
764 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
765 method_id${PARAMS_IN_CALL})${POST_CALL};
769 if called_by_native
.static
or called_by_native
.is_constructor
:
770 first_param_in_declaration
= ''
771 first_param_in_call
= ('g_%s_clazz' %
772 (called_by_native
.java_class_name
or
775 first_param_in_declaration
= ', jobject obj'
776 first_param_in_call
= 'obj'
777 params_in_declaration
= self
.GetCalledByNativeParamsInDeclaration(
779 if params_in_declaration
:
780 params_in_declaration
= ', ' + params_in_declaration
781 params_for_call
= ', '.join(param
.name
782 for param
in called_by_native
.params
)
784 params_for_call
= ', ' + params_for_call
787 if called_by_native
.static_cast
:
788 pre_call
= 'static_cast<%s>(' % called_by_native
.static_cast
791 if not called_by_native
.unchecked
:
792 check_exception
= 'base::android::CheckException(env);'
793 return_type
= JavaDataTypeToC(called_by_native
.return_type
)
794 return_declaration
= ''
796 if return_type
!= 'void':
797 pre_call
= ' ' + pre_call
798 return_declaration
= return_type
+ ' ret ='
799 if re
.match(RE_SCOPED_JNI_RETURN_TYPES
, return_type
):
800 return_type
= 'ScopedJavaLocalRef<' + return_type
+ '>'
801 return_clause
= 'return ' + return_type
+ '(env, ret);'
803 return_clause
= 'return ret;'
805 'JAVA_CLASS': called_by_native
.java_class_name
or self
.class_name
,
806 'METHOD': called_by_native
.name
,
807 'RETURN_TYPE': return_type
,
808 'RETURN_DECLARATION': return_declaration
,
809 'RETURN_CLAUSE': return_clause
,
810 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration
,
811 'PARAMS_IN_DECLARATION': params_in_declaration
,
812 'STATIC': 'Static' if called_by_native
.static
else '',
813 'PRE_CALL': pre_call
,
814 'POST_CALL': post_call
,
815 'ENV_CALL': called_by_native
.env_call
,
816 'FIRST_PARAM_IN_CALL': first_param_in_call
,
817 'PARAMS_IN_CALL': params_for_call
,
818 'METHOD_ID_VAR_NAME': called_by_native
.method_id_var_name
,
819 'CHECK_EXCEPTION': check_exception
,
820 'GET_METHOD_ID_IMPL': self
.GetMethodIDImpl(called_by_native
)
822 values
['FUNCTION_SIGNATURE'] = (
823 function_signature_template
.substitute(values
))
824 if called_by_native
.system_class
:
825 values
['FUNCTION_HEADER'] = (
826 function_header_with_unused_template
.substitute(values
))
828 values
['FUNCTION_HEADER'] = function_header_template
.substitute(values
)
829 return template
.substitute(values
)
831 def GetKMethodArrayEntry(self
, native
):
832 template
= Template("""\
833 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""")
834 values
= {'NAME': native
.name
,
835 'JNI_SIGNATURE': JniParams
.Signature(native
.params
,
838 return template
.substitute(values
)
840 def GetUniqueClasses(self
, origin
):
841 ret
= {self
.class_name
: self
.fully_qualified_class
}
843 class_name
= self
.class_name
844 jni_class_path
= self
.fully_qualified_class
845 if entry
.java_class_name
:
846 class_name
= entry
.java_class_name
847 jni_class_path
= self
.fully_qualified_class
+ '$' + class_name
848 ret
[class_name
] = jni_class_path
851 def GetClassPathDefinitions(self
):
852 """Returns the ClassPath constants."""
854 template
= Template("""\
855 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
856 native_classes
= self
.GetUniqueClasses(self
.natives
)
857 called_by_native_classes
= self
.GetUniqueClasses(self
.called_by_natives
)
858 all_classes
= native_classes
859 all_classes
.update(called_by_native_classes
)
860 for clazz
in all_classes
:
863 'JNI_CLASS_PATH': all_classes
[clazz
],
865 ret
+= [template
.substitute(values
)]
867 for clazz
in called_by_native_classes
:
868 template
= Template("""\
869 // Leaking this jclass as we cannot use LazyInstance from some threads.
870 jclass g_${JAVA_CLASS}_clazz = NULL;""")
874 ret
+= [template
.substitute(values
)]
875 return '\n'.join(ret
)
877 def GetFindClasses(self
):
878 """Returns the imlementation of FindClass for all known classes."""
879 template
= Template("""\
880 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
881 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
883 for clazz
in self
.GetUniqueClasses(self
.called_by_natives
):
884 values
= {'JAVA_CLASS': clazz
}
885 ret
+= [template
.substitute(values
)]
886 return '\n'.join(ret
)
888 def GetMethodIDImpl(self
, called_by_native
):
889 """Returns the implementation of GetMethodID."""
890 template
= Template("""\
891 base::android::MethodID::LazyGet<
892 base::android::MethodID::TYPE_${STATIC}>(
893 env, g_${JAVA_CLASS}_clazz,
896 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
898 jni_name
= called_by_native
.name
899 jni_return_type
= called_by_native
.return_type
900 if called_by_native
.is_constructor
:
902 jni_return_type
= 'void'
904 'JAVA_CLASS': called_by_native
.java_class_name
or self
.class_name
,
905 'JNI_NAME': jni_name
,
906 'METHOD_ID_VAR_NAME': called_by_native
.method_id_var_name
,
907 'STATIC': 'STATIC' if called_by_native
.static
else 'INSTANCE',
908 'JNI_SIGNATURE': JniParams
.Signature(called_by_native
.params
,
912 return template
.substitute(values
)
915 def WrapOutput(output
):
917 for line
in output
.splitlines():
918 # Do not wrap lines under 80 characters or preprocessor directives.
919 if len(line
) < 80 or line
.lstrip()[:1] == '#':
920 stripped
= line
.rstrip()
921 if len(ret
) == 0 or len(ret
[-1]) or len(stripped
):
924 first_line_indent
= ' ' * (len(line
) - len(line
.lstrip()))
925 subsequent_indent
= first_line_indent
+ ' ' * 4
926 if line
.startswith('//'):
927 subsequent_indent
= '//' + subsequent_indent
928 wrapper
= textwrap
.TextWrapper(width
=80,
929 subsequent_indent
=subsequent_indent
,
930 break_long_words
=False)
931 ret
+= [wrapped
.rstrip() for wrapped
in wrapper
.wrap(line
)]
933 return '\n'.join(ret
)
936 def ExtractJarInputFile(jar_file
, input_file
, out_dir
):
937 """Extracts input file from jar and returns the filename.
939 The input file is extracted to the same directory that the generated jni
940 headers will be placed in. This is passed as an argument to script.
943 jar_file: the jar file containing the input files to extract.
944 input_files: the list of files to extract from the jar file.
945 out_dir: the name of the directories to extract to.
948 the name of extracted input file.
950 jar_file
= zipfile
.ZipFile(jar_file
)
952 out_dir
= os
.path
.join(out_dir
, os
.path
.dirname(input_file
))
956 if e
.errno
!= errno
.EEXIST
:
958 extracted_file_name
= os
.path
.join(out_dir
, os
.path
.basename(input_file
))
959 with
open(extracted_file_name
, 'w') as outfile
:
960 outfile
.write(jar_file
.read(input_file
))
962 return extracted_file_name
965 def GenerateJNIHeader(input_file
, output_file
, namespace
, skip_if_same
):
967 if os
.path
.splitext(input_file
)[1] == '.class':
968 jni_from_javap
= JNIFromJavaP
.CreateFromClass(input_file
, namespace
)
969 content
= jni_from_javap
.GetContent()
971 jni_from_java_source
= JNIFromJavaSource
.CreateFromFile(input_file
)
972 content
= jni_from_java_source
.GetContent()
973 except ParseError
, e
:
977 if not os
.path
.exists(os
.path
.dirname(os
.path
.abspath(output_file
))):
978 os
.makedirs(os
.path
.dirname(os
.path
.abspath(output_file
)))
979 if skip_if_same
and os
.path
.exists(output_file
):
980 with
file(output_file
, 'r') as f
:
981 existing_content
= f
.read()
982 if existing_content
== content
:
984 with
file(output_file
, 'w') as f
:
991 usage
= """usage: %prog [OPTIONS]
992 This script will parse the given java source code extracting the native
993 declarations and print the header file to stdout (or a file).
994 See SampleForTests.java for more details.
996 option_parser
= optparse
.OptionParser(usage
=usage
)
997 option_parser
.add_option('-j', dest
='jar_file',
998 help='Extract the list of input files from'
999 ' a specified jar file.'
1000 ' Uses javap to extract the methods from a'
1001 ' pre-compiled class. --input should point'
1002 ' to pre-compiled Java .class files.')
1003 option_parser
.add_option('-n', dest
='namespace',
1004 help='Uses as a namespace in the generated header,'
1005 ' instead of the javap class name.')
1006 option_parser
.add_option('--input_file',
1007 help='Single input file name. The output file name '
1008 'will be derived from it. Must be used with '
1010 option_parser
.add_option('--output_dir',
1011 help='The output directory. Must be used with '
1013 option_parser
.add_option('--optimize_generation', type="int",
1014 default
=0, help='Whether we should optimize JNI '
1015 'generation by not regenerating files if they have '
1017 options
, args
= option_parser
.parse_args(argv
)
1018 if options
.jar_file
:
1019 input_file
= ExtractJarInputFile(options
.jar_file
, options
.input_file
,
1022 input_file
= options
.input_file
1024 if options
.output_dir
:
1025 root_name
= os
.path
.splitext(os
.path
.basename(input_file
))[0]
1026 output_file
= os
.path
.join(options
.output_dir
, root_name
) + '_jni.h'
1027 GenerateJNIHeader(input_file
, output_file
, options
.namespace
,
1028 options
.optimize_generation
)
1031 if __name__
== '__main__':
1032 sys
.exit(main(sys
.argv
))