OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Extracts native methods from a Java file and generates the JNI bindings. | 6 """Extracts native methods from a Java file and generates the JNI bindings. |
7 If you change this, please run and update the tests.""" | 7 If you change this, please run and update the tests.""" |
8 | 8 |
9 import collections | 9 import collections |
10 import optparse | 10 import optparse |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
87 'int': 'jint', | 87 'int': 'jint', |
88 'byte': 'jbyte', | 88 'byte': 'jbyte', |
89 'boolean': 'jboolean', | 89 'boolean': 'jboolean', |
90 'long': 'jlong', | 90 'long': 'jlong', |
91 'double': 'jdouble', | 91 'double': 'jdouble', |
92 'float': 'jfloat', | 92 'float': 'jfloat', |
93 } | 93 } |
94 java_type_map = { | 94 java_type_map = { |
95 'void': 'void', | 95 'void': 'void', |
96 'String': 'jstring', | 96 'String': 'jstring', |
| 97 'java/lang/String': 'jstring', |
97 } | 98 } |
98 if java_type in java_pod_type_map: | 99 if java_type in java_pod_type_map: |
99 return java_pod_type_map[java_type] | 100 return java_pod_type_map[java_type] |
100 elif java_type in java_type_map: | 101 elif java_type in java_type_map: |
101 return java_type_map[java_type] | 102 return java_type_map[java_type] |
102 elif java_type.endswith('[]'): | 103 elif java_type.endswith('[]'): |
103 if java_type[:-2] in java_pod_type_map: | 104 if java_type[:-2] in java_pod_type_map: |
104 return java_pod_type_map[java_type[:-2]] + 'Array' | 105 return java_pod_type_map[java_type[:-2]] + 'Array' |
105 return 'jobjectArray' | 106 return 'jobjectArray' |
106 else: | 107 else: |
107 return 'jobject' | 108 return 'jobject' |
108 | 109 |
109 | 110 |
110 class JniParams(object): | 111 class JniParams(object): |
111 _UNKNOWN_JAVA_TYPE_PREFIX = 'UNKNOWN_JAVA_TYPE: ' | 112 _imports = [] |
112 _external_param_files = set() | 113 _fully_qualified_class = '' |
113 _external_param_list = [] | 114 _package = '' |
| 115 _inner_classes = [] |
114 | 116 |
115 @staticmethod | 117 @staticmethod |
116 def ReadExternalParamList(external_param_files): | 118 def SetFullyQualifiedClass(fully_qualified_class): |
117 if not external_param_files: | 119 JniParams._fully_qualified_class = 'L' + fully_qualified_class |
118 return | 120 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1]) |
119 assert not JniParams._external_param_files | 121 |
120 JniParams._external_param_files = set(external_param_files) | 122 @staticmethod |
121 for external_param_file in JniParams._external_param_files: | 123 def ExtractImportsAndInnerClasses(contents): |
122 with file(external_param_file, 'r') as f: | 124 contents = contents.replace('\n', '') |
123 contents = f.read() | 125 re_import = re.compile(r'import.*?(?P<class>\S*?);') |
124 JniParams._external_param_list += [x.strip() | 126 for match in re.finditer(re_import, contents): |
125 for x in contents.splitlines() | 127 JniParams._imports += ['L' + match.group('class').replace('.', '/')] |
126 if x and not x.startswith('#')] | 128 |
| 129 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W') |
| 130 for match in re.finditer(re_inner, contents): |
| 131 inner = match.group('name') |
| 132 if not JniParams._fully_qualified_class.endswith(inner): |
| 133 JniParams._inner_classes += [JniParams._fully_qualified_class + '$' + |
| 134 inner] |
127 | 135 |
128 @staticmethod | 136 @staticmethod |
129 def JavaToJni(param): | 137 def JavaToJni(param): |
130 """Converts a java param into a JNI signature type.""" | 138 """Converts a java param into a JNI signature type.""" |
131 pod_param_map = { | 139 pod_param_map = { |
132 'int': 'I', | 140 'int': 'I', |
133 'boolean': 'Z', | 141 'boolean': 'Z', |
134 'long': 'J', | 142 'long': 'J', |
135 'double': 'D', | 143 'double': 'D', |
136 'float': 'F', | 144 'float': 'F', |
137 'byte': 'B', | 145 'byte': 'B', |
138 'void': 'V', | 146 'void': 'V', |
139 } | 147 } |
140 object_param_list = [ | 148 object_param_list = [ |
141 'Ljava/lang/Boolean', | 149 'Ljava/lang/Boolean', |
142 'Ljava/lang/Integer', | 150 'Ljava/lang/Integer', |
143 'Ljava/lang/Long', | 151 'Ljava/lang/Long', |
144 'Ljava/lang/Object', | 152 'Ljava/lang/Object', |
145 'Ljava/lang/String', | 153 'Ljava/lang/String', |
146 'Ljava/util/ArrayList', | |
147 'Ljava/util/HashMap', | |
148 'Ljava/util/List', | |
149 'Landroid/content/Context', | |
150 'Landroid/graphics/Bitmap', | |
151 'Landroid/graphics/Canvas', | |
152 'Landroid/graphics/Rect', | |
153 'Landroid/graphics/RectF', | |
154 'Landroid/graphics/Matrix', | |
155 'Landroid/graphics/Point', | |
156 'Landroid/graphics/SurfaceTexture', | |
157 'Landroid/graphics/SurfaceTexture$OnFrameAvailableListener', | |
158 'Landroid/media/MediaPlayer', | |
159 'Landroid/os/Message', | |
160 'Landroid/view/KeyEvent', | |
161 'Landroid/view/Surface', | |
162 'Landroid/view/View', | |
163 'Landroid/webkit/ValueCallback', | |
164 'Ljava/io/InputStream', | |
165 'Ljava/nio/ByteBuffer', | |
166 'Ljava/util/Vector', | |
167 ] | 154 ] |
168 if param == 'byte[][]': | 155 if param == 'byte[][]': |
169 return '[[B' | 156 return '[[B' |
170 prefix = '' | 157 prefix = '' |
171 # Array? | 158 # Array? |
172 if param[-2:] == '[]': | 159 if param[-2:] == '[]': |
173 prefix = '[' | 160 prefix = '[' |
174 param = param[:-2] | 161 param = param[:-2] |
175 # Generic? | 162 # Generic? |
176 if '<' in param: | 163 if '<' in param: |
177 param = param[:param.index('<')] | 164 param = param[:param.index('<')] |
178 if param in pod_param_map: | 165 if param in pod_param_map: |
179 return prefix + pod_param_map[param] | 166 return prefix + pod_param_map[param] |
180 if '/' in param: | 167 if '/' in param: |
181 # Coming from javap, use the fully qualified param directly. | 168 # Coming from javap, use the fully qualified param directly. |
182 return 'L' + param + ';' | 169 return 'L' + param + ';' |
183 for qualified_name in object_param_list + JniParams._external_param_list: | 170 for qualified_name in (object_param_list + |
| 171 JniParams._imports + |
| 172 [JniParams._fully_qualified_class] + |
| 173 JniParams._inner_classes): |
184 if (qualified_name.endswith('/' + param) or | 174 if (qualified_name.endswith('/' + param) or |
185 qualified_name.endswith('$' + param.replace('.', '$')) or | 175 qualified_name.endswith('$' + param.replace('.', '$')) or |
186 qualified_name == 'L' + param): | 176 qualified_name == 'L' + param): |
187 return prefix + qualified_name + ';' | 177 return prefix + qualified_name + ';' |
188 else: | 178 # Type not found, falling back to same package as this class. |
189 return JniParams._UNKNOWN_JAVA_TYPE_PREFIX + prefix + param + ';' | 179 return prefix + 'L' + JniParams._package + '/' + param + ';' |
190 | 180 |
191 @staticmethod | 181 @staticmethod |
192 def Signature(params, returns, wrap): | 182 def Signature(params, returns, wrap): |
193 """Returns the JNI signature for the given datatypes.""" | 183 """Returns the JNI signature for the given datatypes.""" |
194 items = ['('] | 184 items = ['('] |
195 items += [JniParams.JavaToJni(param.datatype) for param in params] | 185 items += [JniParams.JavaToJni(param.datatype) for param in params] |
196 items += [')'] | 186 items += [')'] |
197 items += [JniParams.JavaToJni(returns)] | 187 items += [JniParams.JavaToJni(returns)] |
198 if wrap: | 188 if wrap: |
199 return '\n' + '\n'.join(['"' + item + '"' for item in items]) | 189 return '\n' + '\n'.join(['"' + item + '"' for item in items]) |
(...skipping 10 matching lines...) Expand all Loading... |
210 items = p.split(' ') | 200 items = p.split(' ') |
211 if 'final' in items: | 201 if 'final' in items: |
212 items.remove('final') | 202 items.remove('final') |
213 param = Param( | 203 param = Param( |
214 datatype=items[0], | 204 datatype=items[0], |
215 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), | 205 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), |
216 ) | 206 ) |
217 ret += [param] | 207 ret += [param] |
218 return ret | 208 return ret |
219 | 209 |
220 @staticmethod | |
221 def CheckUnknownDatatypes(fully_qualified_class, items): | |
222 unknown_datatypes = JniParams._GetUnknownDatatypes(items) | |
223 if unknown_datatypes: | |
224 msg = ('There are a few unknown datatypes in %s' % | |
225 fully_qualified_class) | |
226 msg += '\nPlease, edit %s' % str(JniParams._external_param_files) | |
227 msg += '\nand add the JNI type(s):\n' | |
228 for unknown_datatype in unknown_datatypes: | |
229 msg += '\n%s in methods:\n' % unknown_datatype | |
230 msg += '\n '.join(unknown_datatypes[unknown_datatype]) | |
231 raise SyntaxError(msg) | |
232 | |
233 @staticmethod | |
234 def _GetUnknownDatatypes(items): | |
235 """Returns a list containing the unknown datatypes.""" | |
236 unknown_types = {} | |
237 for item in items: | |
238 all_datatypes = ([JniParams.JavaToJni(param.datatype) | |
239 for param in item.params] + | |
240 [JniParams.JavaToJni(item.return_type)]) | |
241 for d in all_datatypes: | |
242 if d.startswith(JniParams._UNKNOWN_JAVA_TYPE_PREFIX): | |
243 unknown_types[d] = (unknown_types.get(d, []) + | |
244 [item.name or 'Unable to parse']) | |
245 return unknown_types | |
246 | |
247 | 210 |
248 def ExtractJNINamespace(contents): | 211 def ExtractJNINamespace(contents): |
249 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') | 212 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') |
250 m = re.findall(re_jni_namespace, contents) | 213 m = re.findall(re_jni_namespace, contents) |
251 if not m: | 214 if not m: |
252 return '' | 215 return '' |
253 return m[0] | 216 return m[0] |
254 | 217 |
255 | 218 |
256 def ExtractFullyQualifiedJavaClassName(java_file_name, contents): | 219 def ExtractFullyQualifiedJavaClassName(java_file_name, contents): |
(...skipping 21 matching lines...) Expand all Loading... |
278 java_class_name=match.group('java_class_name'), | 241 java_class_name=match.group('java_class_name'), |
279 native_class_name=match.group('native_class_name'), | 242 native_class_name=match.group('native_class_name'), |
280 return_type=match.group('return'), | 243 return_type=match.group('return'), |
281 name=match.group('name').replace('native', ''), | 244 name=match.group('name').replace('native', ''), |
282 params=JniParams.Parse(match.group('params'))) | 245 params=JniParams.Parse(match.group('params'))) |
283 natives += [native] | 246 natives += [native] |
284 return natives | 247 return natives |
285 | 248 |
286 | 249 |
287 def GetStaticCastForReturnType(return_type): | 250 def GetStaticCastForReturnType(return_type): |
288 if return_type == 'String': | 251 if return_type in ['String', 'java/lang/String']: |
289 return 'jstring' | 252 return 'jstring' |
| 253 elif return_type.endswith('[]'): |
| 254 return 'jobjectArray' |
290 return None | 255 return None |
291 | 256 |
292 | 257 |
293 def GetEnvCall(is_constructor, is_static, return_type): | 258 def GetEnvCall(is_constructor, is_static, return_type): |
294 """Maps the types availabe via env->Call__Method.""" | 259 """Maps the types availabe via env->Call__Method.""" |
295 if is_constructor: | 260 if is_constructor: |
296 return 'NewObject' | 261 return 'NewObject' |
297 env_call_map = {'boolean': 'Boolean', | 262 env_call_map = {'boolean': 'Boolean', |
298 'byte': 'Byte', | 263 'byte': 'Byte', |
299 'char': 'Char', | 264 'char': 'Char', |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
414 | 379 |
415 class JNIFromJavaP(object): | 380 class JNIFromJavaP(object): |
416 """Uses 'javap' to parse a .class file and generate the JNI header file.""" | 381 """Uses 'javap' to parse a .class file and generate the JNI header file.""" |
417 | 382 |
418 def __init__(self, contents, namespace): | 383 def __init__(self, contents, namespace): |
419 self.contents = contents | 384 self.contents = contents |
420 self.namespace = namespace | 385 self.namespace = namespace |
421 self.fully_qualified_class = re.match('.*?class (?P<class_name>.*?) ', | 386 self.fully_qualified_class = re.match('.*?class (?P<class_name>.*?) ', |
422 contents[1]).group('class_name') | 387 contents[1]).group('class_name') |
423 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') | 388 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') |
| 389 JniParams.SetFullyQualifiedClass(self.fully_qualified_class) |
424 self.java_class_name = self.fully_qualified_class.split('/')[-1] | 390 self.java_class_name = self.fully_qualified_class.split('/')[-1] |
425 if not self.namespace: | 391 if not self.namespace: |
426 self.namespace = 'JNI_' + self.java_class_name | 392 self.namespace = 'JNI_' + self.java_class_name |
427 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\w+?) (?P<name>\w+?)' | 393 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)' |
428 '\((?P<params>.*?)\)') | 394 '\((?P<params>.*?)\)') |
429 self.called_by_natives = [] | 395 self.called_by_natives = [] |
430 for content in contents[2:]: | 396 for content in contents[2:]: |
431 match = re.match(re_method, content) | 397 match = re.match(re_method, content) |
432 if not match: | 398 if not match: |
433 continue | 399 continue |
434 self.called_by_natives += [CalledByNative( | 400 self.called_by_natives += [CalledByNative( |
435 system_class=True, | 401 system_class=True, |
436 unchecked=False, | 402 unchecked=False, |
437 static='static' in match.group('prefix'), | 403 static='static' in match.group('prefix'), |
438 java_class_name='', | 404 java_class_name='', |
439 return_type=match.group('return_type'), | 405 return_type=match.group('return_type').replace('.', '/'), |
440 name=match.group('name'), | 406 name=match.group('name'), |
441 params=JniParams.Parse(match.group('params').replace('.', '/')))] | 407 params=JniParams.Parse(match.group('params').replace('.', '/')))] |
442 re_constructor = re.compile('.*? public ' + | 408 re_constructor = re.compile('.*? public ' + |
443 self.fully_qualified_class.replace('/', '.') + | 409 self.fully_qualified_class.replace('/', '.') + |
444 '\((?P<params>.*?)\)') | 410 '\((?P<params>.*?)\)') |
445 for content in contents[2:]: | 411 for content in contents[2:]: |
446 match = re.match(re_constructor, content) | 412 match = re.match(re_constructor, content) |
447 if not match: | 413 if not match: |
448 continue | 414 continue |
449 self.called_by_natives += [CalledByNative( | 415 self.called_by_natives += [CalledByNative( |
(...skipping 22 matching lines...) Expand all Loading... |
472 stdout, _ = p.communicate() | 438 stdout, _ = p.communicate() |
473 jni_from_javap = JNIFromJavaP(stdout.split('\n'), namespace) | 439 jni_from_javap = JNIFromJavaP(stdout.split('\n'), namespace) |
474 return jni_from_javap | 440 return jni_from_javap |
475 | 441 |
476 | 442 |
477 class JNIFromJavaSource(object): | 443 class JNIFromJavaSource(object): |
478 """Uses the given java source file to generate the JNI header file.""" | 444 """Uses the given java source file to generate the JNI header file.""" |
479 | 445 |
480 def __init__(self, contents, fully_qualified_class): | 446 def __init__(self, contents, fully_qualified_class): |
481 contents = self._RemoveComments(contents) | 447 contents = self._RemoveComments(contents) |
| 448 JniParams.SetFullyQualifiedClass(fully_qualified_class) |
| 449 JniParams.ExtractImportsAndInnerClasses(contents) |
482 jni_namespace = ExtractJNINamespace(contents) | 450 jni_namespace = ExtractJNINamespace(contents) |
483 natives = ExtractNatives(contents) | 451 natives = ExtractNatives(contents) |
484 called_by_natives = ExtractCalledByNatives(contents) | 452 called_by_natives = ExtractCalledByNatives(contents) |
485 if len(natives) == 0 and len(called_by_natives) == 0: | 453 if len(natives) == 0 and len(called_by_natives) == 0: |
486 raise SyntaxError('Unable to find any JNI methods for %s.' % | 454 raise SyntaxError('Unable to find any JNI methods for %s.' % |
487 fully_qualified_class) | 455 fully_qualified_class) |
488 inl_header_file_generator = InlHeaderFileGenerator( | 456 inl_header_file_generator = InlHeaderFileGenerator( |
489 jni_namespace, fully_qualified_class, natives, called_by_natives) | 457 jni_namespace, fully_qualified_class, natives, called_by_natives) |
490 self.content = inl_header_file_generator.GetContent() | 458 self.content = inl_header_file_generator.GetContent() |
491 | 459 |
(...skipping 30 matching lines...) Expand all Loading... |
522 """Generates an inline header file for JNI integration.""" | 490 """Generates an inline header file for JNI integration.""" |
523 | 491 |
524 def __init__(self, namespace, fully_qualified_class, natives, | 492 def __init__(self, namespace, fully_qualified_class, natives, |
525 called_by_natives): | 493 called_by_natives): |
526 self.namespace = namespace | 494 self.namespace = namespace |
527 self.fully_qualified_class = fully_qualified_class | 495 self.fully_qualified_class = fully_qualified_class |
528 self.class_name = self.fully_qualified_class.split('/')[-1] | 496 self.class_name = self.fully_qualified_class.split('/')[-1] |
529 self.natives = natives | 497 self.natives = natives |
530 self.called_by_natives = called_by_natives | 498 self.called_by_natives = called_by_natives |
531 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' | 499 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' |
532 JniParams.CheckUnknownDatatypes(self.fully_qualified_class, | |
533 self.natives + self.called_by_natives) | |
534 | 500 |
535 def GetContent(self): | 501 def GetContent(self): |
536 """Returns the content of the JNI binding file.""" | 502 """Returns the content of the JNI binding file.""" |
537 template = Template("""\ | 503 template = Template("""\ |
538 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 504 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
539 // Use of this source code is governed by a BSD-style license that can be | 505 // Use of this source code is governed by a BSD-style license that can be |
540 // found in the LICENSE file. | 506 // found in the LICENSE file. |
541 | 507 |
542 | 508 |
543 // This file is autogenerated by | 509 // This file is autogenerated by |
(...skipping 443 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
987 option_parser.add_option('-n', dest='namespace', | 953 option_parser.add_option('-n', dest='namespace', |
988 help='Uses as a namespace in the generated header,' | 954 help='Uses as a namespace in the generated header,' |
989 ' instead of the javap class name.') | 955 ' instead of the javap class name.') |
990 option_parser.add_option('--input_file', | 956 option_parser.add_option('--input_file', |
991 help='Single input file name. The output file name ' | 957 help='Single input file name. The output file name ' |
992 'will be derived from it. Must be used with ' | 958 'will be derived from it. Must be used with ' |
993 '--output_dir.') | 959 '--output_dir.') |
994 option_parser.add_option('--output_dir', | 960 option_parser.add_option('--output_dir', |
995 help='The output directory. Must be used with ' | 961 help='The output directory. Must be used with ' |
996 '--input') | 962 '--input') |
997 option_parser.add_option('--external_param_list', | |
998 help='A file name containing a list with extra ' | |
999 'fully-qualified param names. Can be used multiple ' | |
1000 'times.', | |
1001 action='append') | |
1002 options, args = option_parser.parse_args(argv) | 963 options, args = option_parser.parse_args(argv) |
1003 JniParams.ReadExternalParamList(options.external_param_list) | |
1004 if options.jar_file: | 964 if options.jar_file: |
1005 input_file = ExtractJarInputFile(options.jar_file, options.input_file, | 965 input_file = ExtractJarInputFile(options.jar_file, options.input_file, |
1006 options.output_dir) | 966 options.output_dir) |
1007 else: | 967 else: |
1008 input_file = options.input_file | 968 input_file = options.input_file |
1009 output_file = None | 969 output_file = None |
1010 if options.output_dir: | 970 if options.output_dir: |
1011 root_name = os.path.splitext(os.path.basename(input_file))[0] | 971 root_name = os.path.splitext(os.path.basename(input_file))[0] |
1012 output_file = os.path.join(options.output_dir, root_name) + '_jni.h' | 972 output_file = os.path.join(options.output_dir, root_name) + '_jni.h' |
1013 GenerateJNIHeader(input_file, output_file, options.namespace) | 973 GenerateJNIHeader(input_file, output_file, options.namespace) |
1014 | 974 |
1015 | 975 |
1016 if __name__ == '__main__': | 976 if __name__ == '__main__': |
1017 sys.exit(main(sys.argv)) | 977 sys.exit(main(sys.argv)) |
OLD | NEW |