| 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 |