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
== kwargs
.get('ptr_type', '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
.signature
= kwargs
.get('signature')
80 self
.is_constructor
= kwargs
.get('is_constructor', False)
81 self
.env_call
= GetEnvCall(self
.is_constructor
, self
.static
,
83 self
.static_cast
= GetStaticCastForReturnType(self
.return_type
)
86 def JavaDataTypeToC(java_type
):
87 """Returns a C datatype for the given java type."""
93 'boolean': 'jboolean',
101 'java/lang/String': 'jstring',
103 'java/lang/Class': 'jclass',
106 if java_type
in java_pod_type_map
:
107 return java_pod_type_map
[java_type
]
108 elif java_type
in java_type_map
:
109 return java_type_map
[java_type
]
110 elif java_type
.endswith('[]'):
111 if java_type
[:-2] in java_pod_type_map
:
112 return java_pod_type_map
[java_type
[:-2]] + 'Array'
113 return 'jobjectArray'
118 class JniParams(object):
120 _fully_qualified_class
= ''
126 def SetFullyQualifiedClass(fully_qualified_class
):
127 JniParams
._fully
_qualified
_class
= 'L' + fully_qualified_class
128 JniParams
._package
= '/'.join(fully_qualified_class
.split('/')[:-1])
131 def ExtractImportsAndInnerClasses(contents
):
132 contents
= contents
.replace('\n', '')
133 re_import
= re
.compile(r
'import.*?(?P<class>\S*?);')
134 for match
in re
.finditer(re_import
, contents
):
135 JniParams
._imports
+= ['L' + match
.group('class').replace('.', '/')]
137 re_inner
= re
.compile(r
'(class|interface)\s+?(?P<name>\w+?)\W')
138 for match
in re
.finditer(re_inner
, contents
):
139 inner
= match
.group('name')
140 if not JniParams
._fully
_qualified
_class
.endswith(inner
):
141 JniParams
._inner
_classes
+= [JniParams
._fully
_qualified
_class
+ '$' +
145 def ParseJavaPSignature(signature_line
):
146 prefix
= 'Signature: '
147 return '"%s"' % signature_line
[signature_line
.index(prefix
) + len(prefix
):]
150 def JavaToJni(param
):
151 """Converts a java param into a JNI signature type."""
163 object_param_list
= [
164 'Ljava/lang/Boolean',
165 'Ljava/lang/Integer',
173 while param
[-2:] == '[]':
178 param
= param
[:param
.index('<')]
179 if param
in pod_param_map
:
180 return prefix
+ pod_param_map
[param
]
182 # Coming from javap, use the fully qualified param directly.
183 return prefix
+ 'L' + JniParams
.RemapClassName(param
) + ';'
184 for qualified_name
in (object_param_list
+
185 [JniParams
._fully
_qualified
_class
] +
186 JniParams
._inner
_classes
):
187 if (qualified_name
.endswith('/' + param
) or
188 qualified_name
.endswith('$' + param
.replace('.', '$')) or
189 qualified_name
== 'L' + param
):
190 return prefix
+ JniParams
.RemapClassName(qualified_name
) + ';'
192 # Is it from an import? (e.g. referecing Class from import pkg.Class;
193 # note that referencing an inner class Inner from import pkg.Class.Inner
195 for qualified_name
in JniParams
._imports
:
196 if qualified_name
.endswith('/' + param
):
197 # Ensure it's not an inner class.
198 components
= qualified_name
.split('/')
199 if len(components
) > 2 and components
[-2][0].isupper():
200 raise SyntaxError('Inner class (%s) can not be imported '
201 'and used by JNI (%s). Please import the outer '
202 'class and use Outer.Inner instead.' %
203 (qualified_name
, param
))
204 return prefix
+ JniParams
.RemapClassName(qualified_name
) + ';'
206 # Is it an inner class from an outer class import? (e.g. referencing
207 # Class.Inner from import pkg.Class).
209 components
= param
.split('.')
210 outer
= '/'.join(components
[:-1])
211 inner
= components
[-1]
212 for qualified_name
in JniParams
._imports
:
213 if qualified_name
.endswith('/' + outer
):
214 return (prefix
+ JniParams
.RemapClassName(qualified_name
) +
217 # Type not found, falling back to same package as this class.
218 return (prefix
+ 'L' +
219 JniParams
.RemapClassName(JniParams
._package
+ '/' + param
) + ';')
222 def Signature(params
, returns
, wrap
):
223 """Returns the JNI signature for the given datatypes."""
225 items
+= [JniParams
.JavaToJni(param
.datatype
) for param
in params
]
227 items
+= [JniParams
.JavaToJni(returns
)]
229 return '\n' + '\n'.join(['"' + item
+ '"' for item
in items
])
231 return '"' + ''.join(items
) + '"'
235 """Parses the params into a list of Param objects."""
239 for p
in [p
.strip() for p
in params
.split(',')]:
242 items
.remove('final')
245 name
=(items
[1] if len(items
) > 1 else 'p%s' % len(ret
)),
251 def RemapClassName(class_name
):
252 """Remaps class names using the jarjar mapping table."""
253 for old
, new
in JniParams
._remappings
:
254 if old
in class_name
:
255 return class_name
.replace(old
, new
, 1)
259 def SetJarJarMappings(mappings
):
260 """Parse jarjar mappings from a string."""
261 JniParams
._remappings
= []
262 for line
in mappings
.splitlines():
263 keyword
, src
, dest
= line
.split()
264 if keyword
!= 'rule':
266 assert src
.endswith('.**')
267 src
= src
[:-2].replace('.', '/')
268 dest
= dest
.replace('.', '/')
269 if dest
.endswith('@0'):
270 JniParams
._remappings
.append((src
, dest
[:-2] + src
))
272 assert dest
.endswith('@1')
273 JniParams
._remappings
.append((src
, dest
[:-2]))
276 def ExtractJNINamespace(contents
):
277 re_jni_namespace
= re
.compile('.*?@JNINamespace\("(.*?)"\)')
278 m
= re
.findall(re_jni_namespace
, contents
)
284 def ExtractFullyQualifiedJavaClassName(java_file_name
, contents
):
285 re_package
= re
.compile('.*?package (.*?);')
286 matches
= re
.findall(re_package
, contents
)
288 raise SyntaxError('Unable to find "package" line in %s' % java_file_name
)
289 return (matches
[0].replace('.', '/') + '/' +
290 os
.path
.splitext(os
.path
.basename(java_file_name
))[0])
293 def ExtractNatives(contents
, ptr_type
):
294 """Returns a list of dict containing information about a native method."""
295 contents
= contents
.replace('\n', '')
297 re_native
= re
.compile(r
'(@NativeClassQualifiedName'
298 '\(\"(?P<native_class_name>.*?)\"\))?\s*'
299 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\)))?\s*'
300 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*?native '
301 '(?P<return_type>\S*?) '
302 '(?P<name>\w+?)\((?P<params>.*?)\);')
303 for match
in re
.finditer(re_native
, contents
):
304 native
= NativeMethod(
305 static
='static' in match
.group('qualifiers'),
306 java_class_name
=match
.group('java_class_name'),
307 native_class_name
=match
.group('native_class_name'),
308 return_type
=match
.group('return_type'),
309 name
=match
.group('name').replace('native', ''),
310 params
=JniParams
.Parse(match
.group('params')),
316 def GetStaticCastForReturnType(return_type
):
317 type_map
= { 'String' : 'jstring',
318 'java/lang/String' : 'jstring',
319 'boolean[]': 'jbooleanArray',
320 'byte[]': 'jbyteArray',
321 'char[]': 'jcharArray',
322 'short[]': 'jshortArray',
323 'int[]': 'jintArray',
324 'long[]': 'jlongArray',
325 'double[]': 'jdoubleArray' }
326 ret
= type_map
.get(return_type
, None)
329 if return_type
.endswith('[]'):
330 return 'jobjectArray'
334 def GetEnvCall(is_constructor
, is_static
, return_type
):
335 """Maps the types availabe via env->Call__Method."""
338 env_call_map
= {'boolean': 'Boolean',
349 call
= env_call_map
.get(return_type
, 'Object')
351 call
= 'Static' + call
352 return 'Call' + call
+ 'Method'
355 def GetMangledParam(datatype
):
356 """Returns a mangled identifier for the datatype."""
357 if len(datatype
) <= 2:
358 return datatype
.replace('[', 'A')
360 for i
in range(1, len(datatype
)):
364 elif c
.isupper() or datatype
[i
- 1] in ['/', 'L']:
369 def GetMangledMethodName(name
, params
, return_type
):
370 """Returns a mangled method name for the given signature.
372 The returned name can be used as a C identifier and will be unique for all
373 valid overloads of the same method.
377 params: list of Param.
384 for datatype
in [return_type
] + [x
.datatype
for x
in params
]:
385 mangled_items
+= [GetMangledParam(JniParams
.JavaToJni(datatype
))]
386 mangled_name
= name
+ '_'.join(mangled_items
)
387 assert re
.match(r
'[0-9a-zA-Z_]+', mangled_name
)
391 def MangleCalledByNatives(called_by_natives
):
392 """Mangles all the overloads from the call_by_natives list."""
393 method_counts
= collections
.defaultdict(
394 lambda: collections
.defaultdict(lambda: 0))
395 for called_by_native
in called_by_natives
:
396 java_class_name
= called_by_native
.java_class_name
397 name
= called_by_native
.name
398 method_counts
[java_class_name
][name
] += 1
399 for called_by_native
in called_by_natives
:
400 java_class_name
= called_by_native
.java_class_name
401 method_name
= called_by_native
.name
402 method_id_var_name
= method_name
403 if method_counts
[java_class_name
][method_name
] > 1:
404 method_id_var_name
= GetMangledMethodName(method_name
,
405 called_by_native
.params
,
406 called_by_native
.return_type
)
407 called_by_native
.method_id_var_name
= method_id_var_name
408 return called_by_natives
411 # Regex to match the JNI return types that should be included in a
412 # ScopedJavaLocalRef.
413 RE_SCOPED_JNI_RETURN_TYPES
= re
.compile('jobject|jclass|jstring|.*Array')
415 # Regex to match a string like "@CalledByNative public void foo(int bar)".
416 RE_CALLED_BY_NATIVE
= re
.compile(
417 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
418 '\s+(?P<prefix>[\w ]*?)'
419 '\s*(?P<return_type>\S+?)'
421 '\s*\((?P<params>[^\)]*)\)')
424 def ExtractCalledByNatives(contents
):
425 """Parses all methods annotated with @CalledByNative.
428 contents: the contents of the java file.
431 A list of dict with information about the annotated methods.
432 TODO(bulach): return a CalledByNative object.
435 ParseError: if unable to parse.
437 called_by_natives
= []
438 for match
in re
.finditer(RE_CALLED_BY_NATIVE
, contents
):
439 called_by_natives
+= [CalledByNative(
441 unchecked
='Unchecked' in match
.group('Unchecked'),
442 static
='static' in match
.group('prefix'),
443 java_class_name
=match
.group('annotation') or '',
444 return_type
=match
.group('return_type'),
445 name
=match
.group('name'),
446 params
=JniParams
.Parse(match
.group('params')))]
447 # Check for any @CalledByNative occurrences that weren't matched.
448 unmatched_lines
= re
.sub(RE_CALLED_BY_NATIVE
, '', contents
).split('\n')
449 for line1
, line2
in zip(unmatched_lines
, unmatched_lines
[1:]):
450 if '@CalledByNative' in line1
:
451 raise ParseError('could not parse @CalledByNative method signature',
453 return MangleCalledByNatives(called_by_natives
)
456 class JNIFromJavaP(object):
457 """Uses 'javap' to parse a .class file and generate the JNI header file."""
459 def __init__(self
, contents
, options
):
460 self
.contents
= contents
461 self
.namespace
= options
.namespace
462 self
.fully_qualified_class
= re
.match(
463 '.*?(class|interface) (?P<class_name>.*?)( |{)',
464 contents
[1]).group('class_name')
465 self
.fully_qualified_class
= self
.fully_qualified_class
.replace('.', '/')
466 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
467 # away the <...> and use the raw class name that Java 6 would've given us.
468 self
.fully_qualified_class
= self
.fully_qualified_class
.split('<', 1)[0]
469 JniParams
.SetFullyQualifiedClass(self
.fully_qualified_class
)
470 self
.java_class_name
= self
.fully_qualified_class
.split('/')[-1]
471 if not self
.namespace
:
472 self
.namespace
= 'JNI_' + self
.java_class_name
473 re_method
= re
.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
474 '\((?P<params>.*?)\)')
475 self
.called_by_natives
= []
476 for lineno
, content
in enumerate(contents
[2:], 2):
477 match
= re
.match(re_method
, content
)
480 self
.called_by_natives
+= [CalledByNative(
483 static
='static' in match
.group('prefix'),
485 return_type
=match
.group('return_type').replace('.', '/'),
486 name
=match
.group('name'),
487 params
=JniParams
.Parse(match
.group('params').replace('.', '/')),
488 signature
=JniParams
.ParseJavaPSignature(contents
[lineno
+ 1]))]
489 re_constructor
= re
.compile('(.*?)public ' +
490 self
.fully_qualified_class
.replace('/', '.') +
491 '\((?P<params>.*?)\)')
492 for lineno
, content
in enumerate(contents
[2:], 2):
493 match
= re
.match(re_constructor
, content
)
496 self
.called_by_natives
+= [CalledByNative(
501 return_type
=self
.fully_qualified_class
,
503 params
=JniParams
.Parse(match
.group('params').replace('.', '/')),
504 signature
=JniParams
.ParseJavaPSignature(contents
[lineno
+ 1]),
505 is_constructor
=True)]
506 self
.called_by_natives
= MangleCalledByNatives(self
.called_by_natives
)
507 self
.inl_header_file_generator
= InlHeaderFileGenerator(
508 self
.namespace
, self
.fully_qualified_class
, [],
509 self
.called_by_natives
, options
)
511 def GetContent(self
):
512 return self
.inl_header_file_generator
.GetContent()
515 def CreateFromClass(class_file
, options
):
516 class_name
= os
.path
.splitext(os
.path
.basename(class_file
))[0]
517 p
= subprocess
.Popen(args
=['javap', '-s', class_name
],
518 cwd
=os
.path
.dirname(class_file
),
519 stdout
=subprocess
.PIPE
,
520 stderr
=subprocess
.PIPE
)
521 stdout
, _
= p
.communicate()
522 jni_from_javap
= JNIFromJavaP(stdout
.split('\n'), options
)
523 return jni_from_javap
526 class JNIFromJavaSource(object):
527 """Uses the given java source file to generate the JNI header file."""
529 def __init__(self
, contents
, fully_qualified_class
, options
):
530 contents
= self
._RemoveComments
(contents
)
531 JniParams
.SetFullyQualifiedClass(fully_qualified_class
)
532 JniParams
.ExtractImportsAndInnerClasses(contents
)
533 jni_namespace
= ExtractJNINamespace(contents
)
534 natives
= ExtractNatives(contents
, options
.ptr_type
)
535 called_by_natives
= ExtractCalledByNatives(contents
)
536 if len(natives
) == 0 and len(called_by_natives
) == 0:
537 raise SyntaxError('Unable to find any JNI methods for %s.' %
538 fully_qualified_class
)
539 inl_header_file_generator
= InlHeaderFileGenerator(
540 jni_namespace
, fully_qualified_class
, natives
, called_by_natives
,
542 self
.content
= inl_header_file_generator
.GetContent()
544 def _RemoveComments(self
, contents
):
545 # We need to support both inline and block comments, and we need to handle
546 # strings that contain '//' or '/*'. Rather than trying to do all that with
547 # regexps, we just pipe the contents through the C preprocessor. We tell cpp
548 # the file has already been preprocessed, so it just removes comments and
549 # doesn't try to parse #include, #pragma etc.
551 # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java
552 # parser. Maybe we could ditch JNIFromJavaSource and just always use
553 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
554 # http://code.google.com/p/chromium/issues/detail?id=138941
555 p
= subprocess
.Popen(args
=['cpp', '-fpreprocessed'],
556 stdin
=subprocess
.PIPE
,
557 stdout
=subprocess
.PIPE
,
558 stderr
=subprocess
.PIPE
)
559 stdout
, _
= p
.communicate(contents
)
562 def GetContent(self
):
566 def CreateFromFile(java_file_name
, options
):
567 contents
= file(java_file_name
).read()
568 fully_qualified_class
= ExtractFullyQualifiedJavaClassName(java_file_name
,
570 return JNIFromJavaSource(contents
, fully_qualified_class
, options
)
573 class InlHeaderFileGenerator(object):
574 """Generates an inline header file for JNI integration."""
576 def __init__(self
, namespace
, fully_qualified_class
, natives
,
577 called_by_natives
, options
):
578 self
.namespace
= namespace
579 self
.fully_qualified_class
= fully_qualified_class
580 self
.class_name
= self
.fully_qualified_class
.split('/')[-1]
581 self
.natives
= natives
582 self
.called_by_natives
= called_by_natives
583 self
.header_guard
= fully_qualified_class
.replace('/', '_') + '_JNI'
584 self
.script_name
= options
.script_name
586 def GetContent(self
):
587 """Returns the content of the JNI binding file."""
588 template
= Template("""\
589 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
590 // Use of this source code is governed by a BSD-style license that can be
591 // found in the LICENSE file.
594 // This file is autogenerated by
597 // ${FULLY_QUALIFIED_CLASS}
599 #ifndef ${HEADER_GUARD}
600 #define ${HEADER_GUARD}
604 #include "base/android/jni_android.h"
605 #include "base/android/scoped_java_ref.h"
606 #include "base/basictypes.h"
607 #include "base/logging.h"
609 using base::android::ScopedJavaLocalRef;
611 // Step 1: forward declarations.
613 $CLASS_PATH_DEFINITIONS
617 $FORWARD_DECLARATIONS
619 // Step 2: method stubs.
622 // Step 3: RegisterNatives.
624 static bool RegisterNativesImpl(JNIEnv* env) {
625 $REGISTER_NATIVES_IMPL
629 #endif // ${HEADER_GUARD}
632 'SCRIPT_NAME': self
.script_name
,
633 'FULLY_QUALIFIED_CLASS': self
.fully_qualified_class
,
634 'CLASS_PATH_DEFINITIONS': self
.GetClassPathDefinitionsString(),
635 'FORWARD_DECLARATIONS': self
.GetForwardDeclarationsString(),
636 'METHOD_STUBS': self
.GetMethodStubsString(),
637 'OPEN_NAMESPACE': self
.GetOpenNamespaceString(),
638 'REGISTER_NATIVES_IMPL': self
.GetRegisterNativesImplString(),
639 'CLOSE_NAMESPACE': self
.GetCloseNamespaceString(),
640 'HEADER_GUARD': self
.header_guard
,
642 return WrapOutput(template
.substitute(values
))
644 def GetClassPathDefinitionsString(self
):
646 ret
+= [self
.GetClassPathDefinitions()]
647 return '\n'.join(ret
)
649 def GetForwardDeclarationsString(self
):
651 for native
in self
.natives
:
652 if native
.type != 'method':
653 ret
+= [self
.GetForwardDeclaration(native
)]
654 return '\n'.join(ret
)
656 def GetMethodStubsString(self
):
658 for native
in self
.natives
:
659 if native
.type == 'method':
660 ret
+= [self
.GetNativeMethodStub(native
)]
661 for called_by_native
in self
.called_by_natives
:
662 ret
+= [self
.GetCalledByNativeMethodStub(called_by_native
)]
663 return '\n'.join(ret
)
665 def GetKMethodsString(self
, clazz
):
667 for native
in self
.natives
:
668 if (native
.java_class_name
== clazz
or
669 (not native
.java_class_name
and clazz
== self
.class_name
)):
670 ret
+= [self
.GetKMethodArrayEntry(native
)]
671 return '\n'.join(ret
)
673 def GetRegisterNativesImplString(self
):
674 """Returns the implementation for RegisterNatives."""
675 template
= Template("""\
676 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
679 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
681 if (env->RegisterNatives(g_${JAVA_CLASS}_clazz,
682 kMethods${JAVA_CLASS},
683 kMethods${JAVA_CLASS}Size) < 0) {
684 LOG(ERROR) << "RegisterNatives failed in " << __FILE__;
688 ret
= [self
.GetFindClasses()]
689 all_classes
= self
.GetUniqueClasses(self
.natives
)
690 all_classes
[self
.class_name
] = self
.fully_qualified_class
691 for clazz
in all_classes
:
692 kmethods
= self
.GetKMethodsString(clazz
)
694 values
= {'JAVA_CLASS': clazz
,
695 'KMETHODS': kmethods
}
696 ret
+= [template
.substitute(values
)]
697 if not ret
: return ''
698 return '\n' + '\n'.join(ret
)
700 def GetOpenNamespaceString(self
):
702 all_namespaces
= ['namespace %s {' % ns
703 for ns
in self
.namespace
.split('::')]
704 return '\n'.join(all_namespaces
)
707 def GetCloseNamespaceString(self
):
709 all_namespaces
= ['} // namespace %s' % ns
710 for ns
in self
.namespace
.split('::')]
711 all_namespaces
.reverse()
712 return '\n'.join(all_namespaces
) + '\n'
715 def GetJNIFirstParam(self
, native
):
717 if native
.type == 'method':
718 ret
= ['jobject obj']
719 elif native
.type == 'function':
721 ret
= ['jclass clazz']
723 ret
= ['jobject obj']
726 def GetParamsInDeclaration(self
, native
):
727 """Returns the params for the stub declaration.
730 native: the native dictionary describing the method.
733 A string containing the params.
735 return ',\n '.join(self
.GetJNIFirstParam(native
) +
736 [JavaDataTypeToC(param
.datatype
) + ' ' +
738 for param
in native
.params
])
740 def GetCalledByNativeParamsInDeclaration(self
, called_by_native
):
741 return ',\n '.join([JavaDataTypeToC(param
.datatype
) + ' ' +
743 for param
in called_by_native
.params
])
745 def GetForwardDeclaration(self
, native
):
746 template
= Template("""
747 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
749 values
= {'RETURN': JavaDataTypeToC(native
.return_type
),
751 'PARAMS': self
.GetParamsInDeclaration(native
)}
752 return template
.substitute(values
)
754 def GetNativeMethodStub(self
, native
):
755 """Returns stubs for native methods."""
756 template
= Template("""\
757 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {
758 DCHECK(${PARAM0_NAME}) << "${NAME}";
759 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
760 return native->${NAME}(env, obj${PARAMS_IN_CALL})${POST_CALL};
763 params_for_call
= ', '.join(p
.name
for p
in native
.params
[1:])
765 params_for_call
= ', ' + params_for_call
767 return_type
= JavaDataTypeToC(native
.return_type
)
768 if re
.match(RE_SCOPED_JNI_RETURN_TYPES
, return_type
):
769 scoped_return_type
= 'ScopedJavaLocalRef<' + return_type
+ '>'
770 post_call
= '.Release()'
772 scoped_return_type
= return_type
775 'RETURN': return_type
,
776 'SCOPED_RETURN': scoped_return_type
,
778 'PARAMS_IN_DECLARATION': self
.GetParamsInDeclaration(native
),
779 'PARAM0_NAME': native
.params
[0].name
,
780 'P0_TYPE': native
.p0_type
,
781 'PARAMS_IN_CALL': params_for_call
,
782 'POST_CALL': post_call
784 return template
.substitute(values
)
786 def GetCalledByNativeMethodStub(self
, called_by_native
):
787 """Returns a string."""
788 function_signature_template
= Template("""\
789 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
790 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
791 function_header_template
= Template("""\
792 ${FUNCTION_SIGNATURE} {""")
793 function_header_with_unused_template
= Template("""\
794 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
795 ${FUNCTION_SIGNATURE} {""")
796 template
= Template("""
797 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
799 /* Must call RegisterNativesImpl() */
800 DCHECK(g_${JAVA_CLASS}_clazz);
801 jmethodID method_id =
802 ${GET_METHOD_ID_IMPL}
803 ${RETURN_DECLARATION}
804 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
805 method_id${PARAMS_IN_CALL})${POST_CALL};
809 if called_by_native
.static
or called_by_native
.is_constructor
:
810 first_param_in_declaration
= ''
811 first_param_in_call
= ('g_%s_clazz' %
812 (called_by_native
.java_class_name
or
815 first_param_in_declaration
= ', jobject obj'
816 first_param_in_call
= 'obj'
817 params_in_declaration
= self
.GetCalledByNativeParamsInDeclaration(
819 if params_in_declaration
:
820 params_in_declaration
= ', ' + params_in_declaration
821 params_for_call
= ', '.join(param
.name
822 for param
in called_by_native
.params
)
824 params_for_call
= ', ' + params_for_call
827 if called_by_native
.static_cast
:
828 pre_call
= 'static_cast<%s>(' % called_by_native
.static_cast
831 if not called_by_native
.unchecked
:
832 check_exception
= 'base::android::CheckException(env);'
833 return_type
= JavaDataTypeToC(called_by_native
.return_type
)
834 return_declaration
= ''
836 if return_type
!= 'void':
837 pre_call
= ' ' + pre_call
838 return_declaration
= return_type
+ ' ret ='
839 if re
.match(RE_SCOPED_JNI_RETURN_TYPES
, return_type
):
840 return_type
= 'ScopedJavaLocalRef<' + return_type
+ '>'
841 return_clause
= 'return ' + return_type
+ '(env, ret);'
843 return_clause
= 'return ret;'
845 'JAVA_CLASS': called_by_native
.java_class_name
or self
.class_name
,
846 'METHOD': called_by_native
.name
,
847 'RETURN_TYPE': return_type
,
848 'RETURN_DECLARATION': return_declaration
,
849 'RETURN_CLAUSE': return_clause
,
850 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration
,
851 'PARAMS_IN_DECLARATION': params_in_declaration
,
852 'STATIC': 'Static' if called_by_native
.static
else '',
853 'PRE_CALL': pre_call
,
854 'POST_CALL': post_call
,
855 'ENV_CALL': called_by_native
.env_call
,
856 'FIRST_PARAM_IN_CALL': first_param_in_call
,
857 'PARAMS_IN_CALL': params_for_call
,
858 'METHOD_ID_VAR_NAME': called_by_native
.method_id_var_name
,
859 'CHECK_EXCEPTION': check_exception
,
860 'GET_METHOD_ID_IMPL': self
.GetMethodIDImpl(called_by_native
)
862 values
['FUNCTION_SIGNATURE'] = (
863 function_signature_template
.substitute(values
))
864 if called_by_native
.system_class
:
865 values
['FUNCTION_HEADER'] = (
866 function_header_with_unused_template
.substitute(values
))
868 values
['FUNCTION_HEADER'] = function_header_template
.substitute(values
)
869 return template
.substitute(values
)
871 def GetKMethodArrayEntry(self
, native
):
872 template
= Template("""\
873 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""")
874 values
= {'NAME': native
.name
,
875 'JNI_SIGNATURE': JniParams
.Signature(native
.params
,
878 return template
.substitute(values
)
880 def GetUniqueClasses(self
, origin
):
881 ret
= {self
.class_name
: self
.fully_qualified_class
}
883 class_name
= self
.class_name
884 jni_class_path
= self
.fully_qualified_class
885 if entry
.java_class_name
:
886 class_name
= entry
.java_class_name
887 jni_class_path
= self
.fully_qualified_class
+ '$' + class_name
888 ret
[class_name
] = jni_class_path
891 def GetClassPathDefinitions(self
):
892 """Returns the ClassPath constants."""
894 template
= Template("""\
895 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
896 native_classes
= self
.GetUniqueClasses(self
.natives
)
897 called_by_native_classes
= self
.GetUniqueClasses(self
.called_by_natives
)
898 all_classes
= native_classes
899 all_classes
.update(called_by_native_classes
)
900 for clazz
in all_classes
:
903 'JNI_CLASS_PATH': JniParams
.RemapClassName(all_classes
[clazz
]),
905 ret
+= [template
.substitute(values
)]
907 for clazz
in called_by_native_classes
:
908 template
= Template("""\
909 // Leaking this jclass as we cannot use LazyInstance from some threads.
910 jclass g_${JAVA_CLASS}_clazz = NULL;""")
914 ret
+= [template
.substitute(values
)]
915 return '\n'.join(ret
)
917 def GetFindClasses(self
):
918 """Returns the imlementation of FindClass for all known classes."""
919 template
= Template("""\
920 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
921 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
923 for clazz
in self
.GetUniqueClasses(self
.called_by_natives
):
924 values
= {'JAVA_CLASS': clazz
}
925 ret
+= [template
.substitute(values
)]
926 return '\n'.join(ret
)
928 def GetMethodIDImpl(self
, called_by_native
):
929 """Returns the implementation of GetMethodID."""
930 template
= Template("""\
931 base::android::MethodID::LazyGet<
932 base::android::MethodID::TYPE_${STATIC}>(
933 env, g_${JAVA_CLASS}_clazz,
936 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
938 jni_name
= called_by_native
.name
939 jni_return_type
= called_by_native
.return_type
940 if called_by_native
.is_constructor
:
942 jni_return_type
= 'void'
943 if called_by_native
.signature
:
944 signature
= called_by_native
.signature
946 signature
= JniParams
.Signature(called_by_native
.params
,
950 'JAVA_CLASS': called_by_native
.java_class_name
or self
.class_name
,
951 'JNI_NAME': jni_name
,
952 'METHOD_ID_VAR_NAME': called_by_native
.method_id_var_name
,
953 'STATIC': 'STATIC' if called_by_native
.static
else 'INSTANCE',
954 'JNI_SIGNATURE': signature
,
956 return template
.substitute(values
)
959 def WrapOutput(output
):
961 for line
in output
.splitlines():
962 # Do not wrap lines under 80 characters or preprocessor directives.
963 if len(line
) < 80 or line
.lstrip()[:1] == '#':
964 stripped
= line
.rstrip()
965 if len(ret
) == 0 or len(ret
[-1]) or len(stripped
):
968 first_line_indent
= ' ' * (len(line
) - len(line
.lstrip()))
969 subsequent_indent
= first_line_indent
+ ' ' * 4
970 if line
.startswith('//'):
971 subsequent_indent
= '//' + subsequent_indent
972 wrapper
= textwrap
.TextWrapper(width
=80,
973 subsequent_indent
=subsequent_indent
,
974 break_long_words
=False)
975 ret
+= [wrapped
.rstrip() for wrapped
in wrapper
.wrap(line
)]
977 return '\n'.join(ret
)
980 def ExtractJarInputFile(jar_file
, input_file
, out_dir
):
981 """Extracts input file from jar and returns the filename.
983 The input file is extracted to the same directory that the generated jni
984 headers will be placed in. This is passed as an argument to script.
987 jar_file: the jar file containing the input files to extract.
988 input_files: the list of files to extract from the jar file.
989 out_dir: the name of the directories to extract to.
992 the name of extracted input file.
994 jar_file
= zipfile
.ZipFile(jar_file
)
996 out_dir
= os
.path
.join(out_dir
, os
.path
.dirname(input_file
))
1000 if e
.errno
!= errno
.EEXIST
:
1002 extracted_file_name
= os
.path
.join(out_dir
, os
.path
.basename(input_file
))
1003 with
open(extracted_file_name
, 'w') as outfile
:
1004 outfile
.write(jar_file
.read(input_file
))
1006 return extracted_file_name
1009 def GenerateJNIHeader(input_file
, output_file
, options
):
1011 if os
.path
.splitext(input_file
)[1] == '.class':
1012 jni_from_javap
= JNIFromJavaP
.CreateFromClass(input_file
, options
)
1013 content
= jni_from_javap
.GetContent()
1015 jni_from_java_source
= JNIFromJavaSource
.CreateFromFile(
1016 input_file
, options
)
1017 content
= jni_from_java_source
.GetContent()
1018 except ParseError
, e
:
1022 if not os
.path
.exists(os
.path
.dirname(os
.path
.abspath(output_file
))):
1023 os
.makedirs(os
.path
.dirname(os
.path
.abspath(output_file
)))
1024 if options
.optimize_generation
and os
.path
.exists(output_file
):
1025 with
file(output_file
, 'r') as f
:
1026 existing_content
= f
.read()
1027 if existing_content
== content
:
1029 with
file(output_file
, 'w') as f
:
1035 def GetScriptName():
1036 script_components
= os
.path
.abspath(sys
.argv
[0]).split(os
.path
.sep
)
1038 for idx
, value
in enumerate(script_components
):
1039 if value
== 'base' or value
== 'third_party':
1042 return os
.sep
.join(script_components
[base_index
:])
1046 usage
= """usage: %prog [OPTIONS]
1047 This script will parse the given java source code extracting the native
1048 declarations and print the header file to stdout (or a file).
1049 See SampleForTests.java for more details.
1051 option_parser
= optparse
.OptionParser(usage
=usage
)
1052 option_parser
.add_option('-j', dest
='jar_file',
1053 help='Extract the list of input files from'
1054 ' a specified jar file.'
1055 ' Uses javap to extract the methods from a'
1056 ' pre-compiled class. --input should point'
1057 ' to pre-compiled Java .class files.')
1058 option_parser
.add_option('-n', dest
='namespace',
1059 help='Uses as a namespace in the generated header,'
1060 ' instead of the javap class name.')
1061 option_parser
.add_option('--input_file',
1062 help='Single input file name. The output file name '
1063 'will be derived from it. Must be used with '
1065 option_parser
.add_option('--output_dir',
1066 help='The output directory. Must be used with '
1068 option_parser
.add_option('--optimize_generation', type="int",
1069 default
=0, help='Whether we should optimize JNI '
1070 'generation by not regenerating files if they have '
1072 option_parser
.add_option('--jarjar',
1073 help='Path to optional jarjar rules file.')
1074 option_parser
.add_option('--script_name', default
=GetScriptName(),
1075 help='The name of this script in the generated '
1077 option_parser
.add_option('--ptr_type', default
='int',
1078 type='choice', choices
=['int', 'long'],
1079 help='The type used to represent native pointers in '
1080 'Java code. For 32-bit, use int; '
1081 'for 64-bit, use long.')
1082 options
, args
= option_parser
.parse_args(argv
)
1083 if options
.jar_file
:
1084 input_file
= ExtractJarInputFile(options
.jar_file
, options
.input_file
,
1086 elif options
.input_file
:
1087 input_file
= options
.input_file
1089 option_parser
.print_help()
1090 print '\nError: Must specify --jar_file or --input_file.'
1093 if options
.output_dir
:
1094 root_name
= os
.path
.splitext(os
.path
.basename(input_file
))[0]
1095 output_file
= os
.path
.join(options
.output_dir
, root_name
) + '_jni.h'
1097 with
open(options
.jarjar
) as f
:
1098 JniParams
.SetJarJarMappings(f
.read())
1099 GenerateJNIHeader(input_file
, output_file
, options
)
1102 if __name__
== '__main__':
1103 sys
.exit(main(sys
.argv
))