Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
| 2 # | |
| 3 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 4 # Use of this source code is governed by a BSD-style license that can be | |
| 5 # found in the LICENSE file. | |
| 6 | |
| 7 """Extracts native methods from a Java file and generates the JNI bindings. | |
| 8 If you change this, please run and update the tests.""" | |
| 9 | |
| 10 import collections | |
| 11 import optparse | |
| 12 import os | |
| 13 import re | |
| 14 import string | |
| 15 from string import Template | |
| 16 import subprocess | |
| 17 import sys | |
| 18 | |
| 19 | |
| 20 UNKNOWN_JAVA_TYPE_PREFIX = 'UNKNOWN_JAVA_TYPE: ' | |
| 21 | |
| 22 | |
| 23 class ParseError(Exception): | |
| 24 """Exception thrown when we can't parse the input file.""" | |
| 25 | |
| 26 def __init__(self, description, *context_lines): | |
| 27 Exception.__init__(self) | |
| 28 self.description = description | |
| 29 self.context_lines = context_lines | |
| 30 | |
| 31 def __str__(self): | |
| 32 context = '\n'.join(self.context_lines) | |
| 33 return '***\nERROR: %s\n\n%s\n***' % (self.description, context) | |
| 34 | |
| 35 | |
| 36 class Param(object): | |
| 37 """Describes a param for a method, either java or native.""" | |
| 38 | |
| 39 def __init__(self, **kwargs): | |
| 40 self.datatype = kwargs['datatype'] | |
| 41 self.name = kwargs['name'] | |
| 42 self.cpp_class_name = kwargs.get('cpp_class_name', None) | |
| 43 | |
| 44 | |
| 45 class NativeMethod(object): | |
| 46 """Describes a C/C++ method that is called by Java code""" | |
| 47 | |
| 48 def __init__(self, **kwargs): | |
| 49 self.static = kwargs['static'] | |
| 50 self.java_class_name = kwargs['java_class_name'] | |
| 51 self.return_type = kwargs['return_type'] | |
| 52 self.name = kwargs['name'] | |
| 53 self.params = kwargs['params'] | |
| 54 if self.params: | |
| 55 assert type(self.params) is list | |
| 56 assert type(self.params[0]) is Param | |
| 57 if (self.params and | |
| 58 self.params[0].datatype == 'int' and | |
| 59 self.params[0].name.startswith('native')): | |
| 60 self.type = 'method' | |
| 61 if self.params[0].cpp_class_name: | |
| 62 self.p0_type = self.params[0].cpp_class_name | |
| 63 else: | |
| 64 self.p0_type = self.params[0].name[len('native'):] | |
| 65 elif self.static: | |
| 66 self.type = 'function' | |
| 67 else: | |
| 68 self.type = 'function' | |
| 69 self.method_id_var_name = kwargs.get('method_id_var_name', None) | |
| 70 | |
| 71 | |
| 72 class CalledByNative(object): | |
| 73 """Describes a java method exported to c/c++""" | |
| 74 | |
| 75 def __init__(self, **kwargs): | |
| 76 self.system_class = kwargs['system_class'] | |
| 77 self.unchecked = kwargs['unchecked'] | |
| 78 self.static = kwargs['static'] | |
| 79 self.java_class_name = kwargs['java_class_name'] | |
| 80 self.return_type = kwargs['return_type'] | |
| 81 self.env_call = kwargs['env_call'] | |
| 82 self.name = kwargs['name'] | |
| 83 self.params = kwargs['params'] | |
| 84 self.method_id_var_name = kwargs.get('method_id_var_name', None) | |
| 85 | |
| 86 | |
| 87 def JavaDataTypeToC(java_type): | |
| 88 """Returns a C datatype for the given java type.""" | |
| 89 java_pod_type_map = { | |
| 90 'int': 'jint', | |
| 91 'byte': 'jbyte', | |
| 92 'boolean': 'jboolean', | |
| 93 'long': 'jlong', | |
| 94 'double': 'jdouble', | |
| 95 'float': 'jfloat', | |
| 96 } | |
| 97 java_type_map = { | |
| 98 'void': 'void', | |
| 99 'String': 'jstring', | |
| 100 } | |
| 101 if java_type in java_pod_type_map: | |
| 102 return java_pod_type_map[java_type] | |
| 103 elif java_type in java_type_map: | |
| 104 return java_type_map[java_type] | |
| 105 elif java_type.endswith('[]'): | |
| 106 if java_type[:-2] in java_pod_type_map: | |
| 107 return java_pod_type_map[java_type[:-2]] + 'Array' | |
| 108 return 'jobjectArray' | |
| 109 else: | |
| 110 return 'jobject' | |
| 111 | |
| 112 | |
| 113 def JavaParamToJni(param): | |
| 114 """Converts a java param into a JNI signature type.""" | |
| 115 pod_param_map = { | |
| 116 'int': 'I', | |
| 117 'boolean': 'Z', | |
| 118 'long': 'J', | |
| 119 'double': 'D', | |
| 120 'float': 'F', | |
| 121 'byte': 'B', | |
| 122 'void': 'V', | |
| 123 } | |
| 124 object_param_map = { | |
| 125 'String': 'Ljava/lang/String', | |
| 126 'Boolean': 'Ljava/lang/Boolean', | |
| 127 'Integer': 'Ljava/lang/Integer', | |
| 128 'Long': 'Ljava/lang/Long', | |
| 129 'Object': 'Ljava/lang/Object', | |
| 130 'List': 'Ljava/util/List', | |
| 131 'ArrayList': 'Ljava/util/ArrayList', | |
| 132 'HashMap': 'Ljava/util/HashMap', | |
| 133 'Bitmap': 'Landroid/graphics/Bitmap', | |
| 134 'Context': 'Landroid/content/Context', | |
| 135 'Canvas': 'Landroid/graphics/Canvas', | |
| 136 'Surface': 'Landroid/view/Surface', | |
| 137 'KeyEvent': 'Landroid/view/KeyEvent', | |
| 138 'Rect': 'Landroid/graphics/Rect', | |
| 139 'RectF': 'Landroid/graphics/RectF', | |
| 140 'View': 'Landroid/view/View', | |
| 141 'Matrix': 'Landroid/graphics/Matrix', | |
| 142 'Point': 'Landroid/graphics/Point', | |
| 143 'ByteBuffer': 'Ljava/nio/ByteBuffer', | |
| 144 'InputStream': 'Ljava/io/InputStream', | |
| 145 } | |
| 146 app_param_map = { | |
| 147 'ChromeView': | |
| 148 'Lorg/chromium/chromeview/ChromeView', | |
| 149 | |
| 150 'Tab': | |
| 151 'Lcom/android/chrome/Tab', | |
| 152 | |
| 153 'TouchPoint': | |
| 154 'Lorg/chromium/chromeview/TouchPoint', | |
| 155 | |
| 156 'SurfaceTexture': | |
| 157 'Landroid/graphics/SurfaceTexture', | |
| 158 | |
| 159 'ChromeViewClient': | |
| 160 'Lorg/chromium/chromeview/ChromeViewClient', | |
| 161 | |
| 162 'JSModalDialog': | |
| 163 'Lcom/android/chrome/JSModalDialog', | |
| 164 | |
| 165 'NativeInfoBar': | |
| 166 'Lcom/android/chrome/infobar/InfoBarContainer$NativeInfoBar', | |
| 167 | |
| 168 'OmniboxSuggestion': | |
| 169 'Lcom/android/chrome/OmniboxSuggestion', | |
| 170 | |
| 171 'PasswordListObserver': | |
| 172 'Lorg/chromium/chromeview/ChromePreferences$PasswordListObserver', | |
| 173 | |
| 174 'SandboxedProcessArgs': 'Lorg/chromium/chromeview/SandboxedProcessArgs', | |
| 175 | |
| 176 'SandboxedProcessConnection': | |
| 177 'Lorg/chromium/chromeview/SandboxedProcessConnection', | |
| 178 | |
| 179 'SandboxedProcessService': | |
| 180 'Lorg/chromium/chromeview/SandboxedProcessService', | |
| 181 | |
| 182 'BookmarkNode': 'Lcom/android/chrome/ChromeBrowserProvider$BookmarkNode', | |
| 183 | |
| 184 'SQLiteCursor': 'Lcom/android/chrome/database/SQLiteCursor', | |
| 185 | |
| 186 'FindResultReceivedListener.FindNotificationDetails': | |
| 187 'Lorg/chromium/chromeview/ChromeView$FindResultReceivedListener$FindNotifi cationDetails', | |
|
joth
2012/02/14 00:46:49
nit: 80
bulach
2012/02/14 02:12:32
Done.
| |
| 188 | |
| 189 'ChromeViewContextMenuInfo': | |
| 190 'Lorg/chromium/chromeview/ChromeView$ChromeViewContextMenuInfo', | |
| 191 | |
| 192 'AutofillData': 'Lorg/chromium/chromeview/AutofillData', | |
| 193 | |
| 194 'JavaInputStream': 'Lorg/chromium/chromeview/JavaInputStream', | |
| 195 | |
| 196 'ChromeVideoView': 'Lorg/chromium/chromeview/ChromeVideoView', | |
| 197 | |
| 198 'ChromeHttpAuthHandler': 'Lorg/chromium/chromeview/ChromeHttpAuthHandler', | |
| 199 } | |
| 200 if param == 'byte[][]': | |
| 201 return '[[B' | |
| 202 prefix = '' | |
| 203 # Array? | |
| 204 if param[-2:] == '[]': | |
| 205 prefix = '[' | |
| 206 param = param[:-2] | |
| 207 # Generic? | |
| 208 if '<' in param: | |
| 209 param = param[:param.index('<')] | |
| 210 if param in pod_param_map: | |
| 211 return prefix + pod_param_map[param] | |
| 212 elif param in object_param_map: | |
| 213 return prefix + object_param_map[param] + ';' | |
| 214 elif param in app_param_map: | |
| 215 return prefix + app_param_map[param] + ';' | |
| 216 else: | |
| 217 return UNKNOWN_JAVA_TYPE_PREFIX + prefix + param + ';' | |
| 218 | |
| 219 | |
| 220 def JniSignature(params, returns): | |
| 221 """Returns the JNI signature for the given datatypes.""" | |
| 222 ret = '"(' | |
| 223 ret += ''.join([JavaParamToJni(param.datatype) for param in params]) | |
| 224 ret += ')' | |
| 225 ret += JavaParamToJni(returns) | |
| 226 ret += '"' | |
| 227 return ret | |
| 228 | |
| 229 | |
| 230 def ParseParams(params): | |
| 231 """Parses the params into a list of Param objects.""" | |
| 232 if not params: | |
| 233 return [] | |
| 234 ret = [] | |
| 235 re_comment = re.compile(r'.*?\/\* (.*) \*\/') | |
| 236 for p in [p.strip() for p in params.split(',')]: | |
| 237 items = p.split(' ') | |
| 238 if 'final' in items: | |
| 239 items.remove('final') | |
| 240 comment = re.match(re_comment, p) | |
| 241 param = Param( | |
| 242 datatype=items[0], | |
| 243 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), | |
| 244 cpp_class_name=comment.group(1) if comment else None | |
| 245 ) | |
| 246 ret += [param] | |
| 247 return ret | |
| 248 | |
| 249 | |
| 250 def GetUnknownDatatypes(items): | |
| 251 """Returns a list containing the unknown datatypes.""" | |
| 252 unknown_types = {} | |
| 253 for item in items: | |
| 254 all_datatypes = ([JavaParamToJni(param.datatype) | |
| 255 for param in item.params] + | |
| 256 [JavaParamToJni(item.return_type)]) | |
| 257 for d in all_datatypes: | |
| 258 if d.startswith(UNKNOWN_JAVA_TYPE_PREFIX): | |
| 259 unknown_types[d] = (unknown_types.get(d, []) + | |
| 260 [item.name or 'Unable to parse']) | |
| 261 return unknown_types | |
| 262 | |
| 263 | |
| 264 def ExtractFullyQualifiedJavaClassName(java_file_name, contents): | |
| 265 re_package = re.compile('.*?package (.*?);') | |
| 266 matches = re.findall(re_package, contents) | |
| 267 if not matches: | |
| 268 raise SyntaxError('Unable to find "package" line in %s' % java_file_name) | |
| 269 return (matches[0].replace('.', '/') + '/' + | |
| 270 os.path.splitext(os.path.basename(java_file_name))[0]) | |
| 271 | |
| 272 | |
| 273 def ExtractNatives(contents): | |
| 274 """Returns a list of dict containing information about a native method.""" | |
| 275 contents = contents.replace('\n', '') | |
| 276 natives = [] | |
| 277 re_native = re.compile(r'(@NativeCall(\(\"(.*?)\"\)))?\s*' | |
| 278 '(\w+\s\w+|\w+|\s+)\s*?native (\S*?) (\w+?)\((.*?)\);') | |
| 279 matches = re.findall(re_native, contents) | |
| 280 for match in matches: | |
| 281 native = NativeMethod( | |
| 282 static='static' in match[3], | |
| 283 java_class_name=match[2], | |
| 284 return_type=match[4], | |
| 285 name=match[5].replace('native', ''), | |
| 286 params=ParseParams(match[6])) | |
| 287 natives += [native] | |
| 288 return natives | |
| 289 | |
| 290 | |
| 291 def GetEnvCallForReturnType(return_type): | |
| 292 """Maps the types availabe via env->Call__Method.""" | |
| 293 env_call_map = {'boolean': ('Boolean', ''), | |
| 294 'byte': ('Byte', ''), | |
| 295 'char': ('Char', ''), | |
| 296 'short': ('Short', ''), | |
| 297 'int': ('Int', ''), | |
| 298 'long': ('Long', ''), | |
| 299 'float': ('Float', ''), | |
| 300 'void': ('Void', ''), | |
| 301 'double': ('Double', ''), | |
| 302 'String': ('Object', 'jstring'), | |
| 303 'Object': ('Object', ''), | |
| 304 } | |
| 305 return env_call_map.get(return_type, ('Object', '')) | |
| 306 | |
| 307 | |
| 308 def GetMangledMethodName(name, jni_signature): | |
| 309 """Returns a mangled method name for a (name, jni_signature) pair. | |
| 310 | |
| 311 The returned name can be used as a C identifier and will be unique for all | |
| 312 valid overloads of the same method. | |
| 313 | |
| 314 Args: | |
| 315 name: string. | |
| 316 jni_signature: string. | |
| 317 | |
| 318 Returns: | |
| 319 A mangled name. | |
| 320 """ | |
| 321 sig_translation = string.maketrans('[()/;$', 'apq_xs') | |
| 322 mangled_name = name + '_' + string.translate(jni_signature, sig_translation, | |
| 323 '"') | |
| 324 assert re.match(r'[0-9a-zA-Z_]+', mangled_name) | |
| 325 return mangled_name | |
| 326 | |
| 327 | |
| 328 def MangleCalledByNatives(called_by_natives): | |
| 329 """Mangles all the overloads from the call_by_natives list.""" | |
| 330 method_counts = collections.defaultdict( | |
| 331 lambda: collections.defaultdict(lambda: 0)) | |
| 332 for called_by_native in called_by_natives: | |
| 333 java_class_name = called_by_native.java_class_name | |
| 334 name = called_by_native.name | |
| 335 method_counts[java_class_name][name] += 1 | |
| 336 for called_by_native in called_by_natives: | |
| 337 java_class_name = called_by_native.java_class_name | |
| 338 method_name = called_by_native.name | |
| 339 method_id_var_name = method_name | |
| 340 if method_counts[java_class_name][method_name] > 1: | |
| 341 jni_signature = JniSignature(called_by_native.params, | |
| 342 called_by_native.return_type) | |
| 343 method_id_var_name = GetMangledMethodName(method_name, jni_signature) | |
| 344 called_by_native.method_id_var_name = method_id_var_name | |
| 345 return called_by_natives | |
| 346 | |
| 347 | |
| 348 # Regex to match the JNI return types that should be included in a | |
| 349 # ScopedJavaLocalRef. | |
| 350 RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array') | |
| 351 | |
| 352 # Regex to match a string like "@CalledByNative public void foo(int bar)". | |
| 353 RE_CALLED_BY_NATIVE = re.compile( | |
| 354 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?' | |
| 355 '\s+(?P<prefix>[\w ]*?)' | |
| 356 '\s*(?P<return_type>\w+)' | |
| 357 '\s+(?P<name>\w+)' | |
| 358 '\s*\((?P<params>[^\)]*)\)') | |
| 359 | |
| 360 | |
| 361 def ExtractCalledByNatives(contents): | |
| 362 """Parses all methods annotated with @CalledByNative. | |
| 363 | |
| 364 Args: | |
| 365 contents: the contents of the java file. | |
| 366 | |
| 367 Returns: | |
| 368 A list of dict with information about the annotated methods. | |
| 369 TODO(bulach): return a CalledByNative object. | |
| 370 | |
| 371 Raises: | |
| 372 ParseError: if unable to parse. | |
| 373 """ | |
| 374 called_by_natives = [] | |
| 375 for match in re.finditer(RE_CALLED_BY_NATIVE, contents): | |
| 376 called_by_natives += [CalledByNative( | |
| 377 system_class=False, | |
| 378 unchecked='Unchecked' in match.group('Unchecked'), | |
| 379 static='static' in match.group('prefix'), | |
| 380 java_class_name=match.group('annotation') or '', | |
| 381 return_type=match.group('return_type'), | |
| 382 env_call=GetEnvCallForReturnType(match.group('return_type')), | |
| 383 name=match.group('name'), | |
| 384 params=ParseParams(match.group('params')))] | |
| 385 # Check for any @CalledByNative occurrences that weren't matched. | |
| 386 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') | |
| 387 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): | |
| 388 if '@CalledByNative' in line1: | |
| 389 raise ParseError('could not parse @CalledByNative method signature', | |
| 390 line1, line2) | |
| 391 return MangleCalledByNatives(called_by_natives) | |
| 392 | |
| 393 | |
| 394 class JNIFromJavaP(object): | |
| 395 """Uses 'javap' to parse a .class file and generate the JNI header file.""" | |
| 396 | |
| 397 def __init__(self, contents, namespace): | |
| 398 self.contents = contents | |
| 399 self.namespace = namespace | |
| 400 self.fully_qualified_class = re.match('.*?class (.*?) ', | |
| 401 contents[1]).group(1) | |
| 402 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') | |
| 403 self.java_class_name = self.fully_qualified_class.split('/')[-1] | |
| 404 if not self.namespace: | |
| 405 self.namespace = 'JNI_' + self.java_class_name | |
| 406 re_method = re.compile('(.*?)(\w+?) (\w+?)\((.*?)\)') | |
| 407 self.called_by_natives = [] | |
| 408 for method in contents[2:]: | |
| 409 match = re.match(re_method, method) | |
| 410 if not match: | |
| 411 continue | |
| 412 self.called_by_natives += [CalledByNative( | |
| 413 system_class=True, | |
| 414 unchecked=False, | |
| 415 static='static' in match.group(1), | |
| 416 java_class_name='', | |
| 417 return_type=match.group(2), | |
| 418 name=match.group(3), | |
| 419 params=ParseParams(match.group(4)), | |
| 420 env_call=GetEnvCallForReturnType(match.group(2)))] | |
| 421 self.called_by_natives = MangleCalledByNatives(self.called_by_natives) | |
| 422 self.inl_header_file_generator = InlHeaderFileGenerator( | |
| 423 self.namespace, self.fully_qualified_class, [], self.called_by_natives) | |
| 424 | |
| 425 def GetContent(self): | |
| 426 return self.inl_header_file_generator.GetContent() | |
| 427 | |
| 428 @staticmethod | |
| 429 def CreateFromClass(class_file, namespace): | |
| 430 class_name = os.path.splitext(os.path.basename(class_file))[0] | |
| 431 p = subprocess.Popen(args=['javap', class_name], | |
| 432 cwd=os.path.dirname(class_file), | |
| 433 stdout=subprocess.PIPE, | |
| 434 stderr=subprocess.PIPE) | |
| 435 stdout, _ = p.communicate() | |
| 436 jni_from_javap = JNIFromJavaP(stdout.split('\n'), namespace) | |
| 437 return jni_from_javap | |
| 438 | |
| 439 | |
| 440 class JNIFromJavaSource(object): | |
| 441 """Uses the given java source file to generate the JNI header file.""" | |
| 442 | |
| 443 def __init__(self, contents, fully_qualified_class): | |
| 444 contents = self._RemoveComments(contents) | |
| 445 natives = ExtractNatives(contents) | |
| 446 called_by_natives = ExtractCalledByNatives(contents) | |
| 447 inl_header_file_generator = InlHeaderFileGenerator( | |
| 448 '', fully_qualified_class, natives, called_by_natives) | |
| 449 self.content = inl_header_file_generator.GetContent() | |
| 450 | |
| 451 def _RemoveComments(self, contents): | |
| 452 ret = [] | |
| 453 for c in [c.strip() for c in contents.split('\n')]: | |
| 454 if not c.startswith('//'): | |
| 455 ret += [c] | |
| 456 return '\n'.join(ret) | |
| 457 | |
| 458 def GetContent(self): | |
| 459 return self.content | |
| 460 | |
| 461 @staticmethod | |
| 462 def CreateFromFile(java_file_name): | |
| 463 contents = file(java_file_name).read() | |
| 464 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, | |
| 465 contents) | |
| 466 return JNIFromJavaSource(contents, fully_qualified_class) | |
| 467 | |
| 468 | |
| 469 class InlHeaderFileGenerator(object): | |
| 470 """Generates an inline header file for JNI integration.""" | |
| 471 | |
| 472 def __init__(self, namespace, fully_qualified_class, natives, | |
| 473 called_by_natives): | |
| 474 self.namespace = namespace | |
| 475 self.fully_qualified_class = fully_qualified_class | |
| 476 self.class_name = self.fully_qualified_class.split('/')[-1] | |
| 477 self.natives = natives | |
| 478 self.called_by_natives = called_by_natives | |
| 479 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' | |
| 480 unknown_datatypes = GetUnknownDatatypes(self.natives + | |
| 481 self.called_by_natives) | |
| 482 if unknown_datatypes: | |
| 483 msg = ('There are a few unknown datatypes in %s' % | |
| 484 self.fully_qualified_class) | |
| 485 msg += '\nPlease, edit %s' % sys.argv[0] | |
| 486 msg += '\nand add the java type to JavaParamToJni()\n' | |
| 487 for unknown_datatype in unknown_datatypes: | |
| 488 msg += '\n%s in methods:\n' % unknown_datatype | |
| 489 msg += '\n '.join(unknown_datatypes[unknown_datatype]) | |
| 490 raise SyntaxError(msg) | |
| 491 | |
| 492 def GetContent(self): | |
| 493 """Returns the content of the JNI binding file.""" | |
| 494 template = Template("""\ | |
| 495 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 496 // Use of this source code is governed by a BSD-style license that can be | |
| 497 // found in the LICENSE file. | |
| 498 | |
| 499 | |
| 500 // This file is autogenerated by ${SCRIPT_NAME} | |
| 501 // For ${FULLY_QUALIFIED_CLASS} | |
| 502 | |
| 503 #ifndef ${HEADER_GUARD} | |
| 504 #define ${HEADER_GUARD} | |
| 505 | |
| 506 #include <jni.h> | |
| 507 | |
| 508 #include "base/android/jni_android.h" | |
| 509 #include "base/android/scoped_java_ref.h" | |
| 510 #include "base/basictypes.h" | |
| 511 #include "base/logging.h" | |
| 512 | |
| 513 using base::android::ScopedJavaLocalRef; | |
| 514 | |
| 515 // Step 1: forward declarations. | |
| 516 namespace { | |
| 517 $CLASS_PATH_DEFINITIONS | |
| 518 } // namespace | |
| 519 $FORWARD_DECLARATIONS | |
| 520 | |
| 521 // Step 2: method stubs. | |
| 522 $METHOD_STUBS | |
| 523 | |
| 524 // Step 3: GetMethodIDs and RegisterNatives. | |
| 525 $OPEN_NAMESPACE | |
| 526 | |
| 527 static void GetMethodIDsImpl(JNIEnv* env) { | |
| 528 $GET_METHOD_IDS_IMPL | |
| 529 } | |
| 530 | |
| 531 static bool RegisterNativesImpl(JNIEnv* env) { | |
| 532 ${NAMESPACE}GetMethodIDsImpl(env); | |
| 533 $REGISTER_NATIVES_IMPL | |
| 534 return true; | |
| 535 } | |
| 536 $CLOSE_NAMESPACE | |
| 537 #endif // ${HEADER_GUARD} | |
| 538 """) | |
| 539 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) | |
| 540 base_index = script_components.index('base') | |
| 541 script_name = os.sep.join(script_components[base_index:]) | |
| 542 values = { | |
| 543 'SCRIPT_NAME': script_name, | |
| 544 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, | |
| 545 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), | |
| 546 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(), | |
| 547 'METHOD_STUBS': self.GetMethodStubsString(), | |
| 548 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), | |
| 549 'NAMESPACE': self.GetNamespaceString(), | |
| 550 'GET_METHOD_IDS_IMPL': self.GetMethodIDsImplString(), | |
| 551 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString(), | |
| 552 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), | |
| 553 'HEADER_GUARD': self.header_guard, | |
| 554 } | |
| 555 return template.substitute(values) | |
| 556 | |
| 557 def GetClassPathDefinitionsString(self): | |
| 558 ret = [] | |
| 559 ret += [self.GetClassPathDefinitions()] | |
| 560 return '\n'.join(ret) | |
| 561 | |
| 562 def GetForwardDeclarationsString(self): | |
| 563 ret = [] | |
| 564 for native in self.natives: | |
| 565 if native.type != 'method': | |
| 566 ret += [self.GetForwardDeclaration(native)] | |
| 567 return '\n'.join(ret) | |
| 568 | |
| 569 def GetMethodStubsString(self): | |
| 570 ret = [] | |
| 571 for native in self.natives: | |
| 572 if native.type == 'method': | |
| 573 ret += [self.GetNativeMethodStub(native)] | |
| 574 for called_by_native in self.called_by_natives: | |
| 575 ret += [self.GetCalledByNativeMethodStub(called_by_native)] | |
| 576 return '\n'.join(ret) | |
| 577 | |
| 578 def GetKMethodsString(self, clazz): | |
| 579 ret = [] | |
| 580 for native in self.natives: | |
| 581 if (native.java_class_name == clazz or | |
| 582 (not native.java_class_name and clazz == self.class_name)): | |
| 583 ret += [self.GetKMethodArrayEntry(native)] | |
| 584 return '\n'.join(ret) | |
| 585 | |
| 586 def GetMethodIDsImplString(self): | |
| 587 ret = [] | |
| 588 ret += [self.GetFindClasses()] | |
| 589 for called_by_native in self.called_by_natives: | |
| 590 ret += [self.GetMethodIDImpl(called_by_native)] | |
| 591 return '\n'.join(ret) | |
| 592 | |
| 593 def GetRegisterNativesImplString(self): | |
| 594 """Returns the implementation for RegisterNatives.""" | |
| 595 template = Template("""\ | |
| 596 static const JNINativeMethod kMethods${JAVA_CLASS}[] = { | |
| 597 ${KMETHODS} | |
| 598 }; | |
| 599 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS}); | |
| 600 | |
| 601 if (env->RegisterNatives(g_${JAVA_CLASS}_clazz.obj(), | |
| 602 kMethods${JAVA_CLASS}, | |
| 603 kMethods${JAVA_CLASS}Size) < 0) { | |
| 604 LOG(ERROR) << "RegisterNatives failed in " << __FILE__; | |
| 605 return false; | |
| 606 } | |
| 607 """) | |
| 608 ret = [] | |
| 609 all_classes = self.GetUniqueClasses(self.natives) | |
| 610 all_classes[self.class_name] = self.fully_qualified_class | |
| 611 for clazz in all_classes: | |
| 612 kmethods = self.GetKMethodsString(clazz) | |
| 613 if kmethods: | |
| 614 values = {'JAVA_CLASS': clazz, | |
| 615 'KMETHODS': kmethods} | |
| 616 ret += [template.substitute(values)] | |
| 617 if not ret: return '' | |
| 618 return '\n' + '\n'.join(ret) | |
| 619 | |
| 620 def GetOpenNamespaceString(self): | |
| 621 if self.namespace: | |
| 622 return 'namespace %s {' % self.namespace | |
| 623 return '' | |
| 624 | |
| 625 def GetNamespaceString(self): | |
| 626 if self.namespace: | |
| 627 return '%s::' % self.namespace | |
| 628 return '' | |
| 629 | |
| 630 def GetCloseNamespaceString(self): | |
| 631 if self.namespace: | |
| 632 return '} // namespace %s\n' % self.namespace | |
| 633 return '' | |
| 634 | |
| 635 def GetJNIFirstParam(self, native): | |
| 636 ret = [] | |
| 637 if native.type == 'method': | |
| 638 ret = ['jobject obj'] | |
| 639 elif native.type == 'function': | |
| 640 if native.static: | |
| 641 ret = ['jclass clazz'] | |
| 642 else: | |
| 643 ret = ['jobject obj'] | |
| 644 return ret | |
| 645 | |
| 646 def GetParamsInDeclaration(self, native): | |
| 647 """Returns the params for the stub declaration. | |
| 648 | |
| 649 Args: | |
| 650 native: the native dictionary describing the method. | |
| 651 | |
| 652 Returns: | |
| 653 A string containing the params. | |
| 654 """ | |
| 655 return ',\n '.join(self.GetJNIFirstParam(native) + | |
| 656 [JavaDataTypeToC(param.datatype) + ' ' + | |
| 657 param.name | |
| 658 for param in native.params]) | |
| 659 | |
| 660 def GetCalledByNativeParamsInDeclaration(self, called_by_native): | |
| 661 return ',\n '.join([JavaDataTypeToC(param.datatype) + ' ' + | |
| 662 param.name | |
| 663 for param in called_by_native.params]) | |
| 664 | |
| 665 def GetForwardDeclaration(self, native): | |
| 666 template = Template(""" | |
| 667 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS}); | |
| 668 """) | |
| 669 values = {'RETURN': JavaDataTypeToC(native.return_type), | |
| 670 'NAME': native.name, | |
| 671 'PARAMS': self.GetParamsInDeclaration(native)} | |
| 672 return template.substitute(values) | |
| 673 | |
| 674 def GetNativeMethodStub(self, native): | |
| 675 """Returns stubs for native methods.""" | |
| 676 template = Template("""\ | |
| 677 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) { | |
| 678 DCHECK(${PARAM0_NAME}) << "${NAME}"; | |
| 679 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); | |
| 680 return native->${NAME}(env, obj${PARAMS_IN_CALL})${POST_CALL}; | |
| 681 } | |
| 682 """) | |
| 683 params_for_call = ', '.join(p.name for p in native.params[1:]) | |
| 684 if params_for_call: | |
| 685 params_for_call = ', ' + params_for_call | |
| 686 | |
| 687 return_type = JavaDataTypeToC(native.return_type) | |
| 688 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): | |
| 689 scoped_return_type = 'ScopedJavaLocalRef<' + return_type + '>' | |
| 690 post_call = '.Release()' | |
| 691 else: | |
| 692 scoped_return_type = return_type | |
| 693 post_call = '' | |
| 694 values = { | |
| 695 'RETURN': return_type, | |
| 696 'SCOPED_RETURN': scoped_return_type, | |
| 697 'NAME': native.name, | |
| 698 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native), | |
| 699 'PARAM0_NAME': native.params[0].name, | |
| 700 'P0_TYPE': native.p0_type, | |
| 701 'PARAMS_IN_CALL': params_for_call, | |
| 702 'POST_CALL': post_call | |
| 703 } | |
| 704 return template.substitute(values) | |
| 705 | |
| 706 def GetCalledByNativeMethodStub(self, called_by_native): | |
| 707 """Returns a string.""" | |
| 708 function_signature_template = Template("""\ | |
| 709 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD}(JNIEnv* env${FIRST_PARAM_IN_D ECLARATION}${PARAMS_IN_DECLARATION})""") | |
| 710 function_header_template = Template("""\ | |
| 711 ${FUNCTION_SIGNATURE} {""") | |
| 712 function_header_with_unused_template = Template("""\ | |
| 713 ${FUNCTION_SIGNATURE} __attribute__ ((unused)); | |
| 714 ${FUNCTION_SIGNATURE} {""") | |
| 715 template = Template(""" | |
| 716 static jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0; | |
| 717 ${FUNCTION_HEADER} | |
| 718 DCHECK(!g_${JAVA_CLASS}_clazz.is_null()); /* Must call RegisterNativesImpl() */ | |
| 719 DCHECK(g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); | |
| 720 ${RETURN_DECLARATION} | |
| 721 ${PRE_CALL}env->Call${STATIC}${ENV_CALL}Method(${FIRST_PARAM_IN_CALL}, | |
| 722 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL}; | |
| 723 ${CHECK_EXCEPTION} | |
| 724 ${RETURN_CLAUSE} | |
| 725 }""") | |
| 726 if called_by_native.static: | |
| 727 first_param_in_declaration = '' | |
| 728 first_param_in_call = ('g_%s_clazz.obj()' % | |
| 729 (called_by_native.java_class_name or | |
| 730 self.class_name)) | |
| 731 else: | |
| 732 first_param_in_declaration = ', jobject obj' | |
| 733 first_param_in_call = 'obj' | |
| 734 params_in_declaration = self.GetCalledByNativeParamsInDeclaration( | |
| 735 called_by_native) | |
| 736 if params_in_declaration: | |
| 737 params_in_declaration = ', ' + params_in_declaration | |
| 738 params_for_call = ', '.join(param.name | |
| 739 for param in called_by_native.params) | |
| 740 if params_for_call: | |
| 741 params_for_call = ', ' + params_for_call | |
| 742 pre_call = '' | |
| 743 post_call = '' | |
| 744 if called_by_native.env_call[1]: | |
| 745 pre_call = 'static_cast<%s>(' % called_by_native.env_call[1] | |
| 746 post_call = ')' | |
| 747 check_exception = '' | |
| 748 if not called_by_native.unchecked: | |
| 749 check_exception = 'base::android::CheckException(env);' | |
| 750 return_type = JavaDataTypeToC(called_by_native.return_type) | |
| 751 return_declaration = '' | |
| 752 return_clause = '' | |
| 753 if return_type != 'void': | |
| 754 pre_call = ' ' + pre_call | |
| 755 return_declaration = return_type + ' ret =' | |
| 756 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): | |
| 757 return_type = 'ScopedJavaLocalRef<' + return_type + '>' | |
| 758 return_clause = 'return ' + return_type + '(env, ret);' | |
| 759 else: | |
| 760 return_clause = 'return ret;' | |
| 761 values = { | |
| 762 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, | |
| 763 'METHOD': called_by_native.name, | |
| 764 'RETURN_TYPE': return_type, | |
| 765 'RETURN_DECLARATION': return_declaration, | |
| 766 'RETURN_CLAUSE': return_clause, | |
| 767 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, | |
| 768 'PARAMS_IN_DECLARATION': params_in_declaration, | |
| 769 'STATIC': 'Static' if called_by_native.static else '', | |
| 770 'PRE_CALL': pre_call, | |
| 771 'POST_CALL': post_call, | |
| 772 'ENV_CALL': called_by_native.env_call[0], | |
| 773 'FIRST_PARAM_IN_CALL': first_param_in_call, | |
| 774 'PARAMS_IN_CALL': params_for_call, | |
| 775 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, | |
| 776 'CHECK_EXCEPTION': check_exception, | |
| 777 } | |
| 778 values['FUNCTION_SIGNATURE'] = ( | |
| 779 function_signature_template.substitute(values)) | |
| 780 if called_by_native.system_class: | |
| 781 values['FUNCTION_HEADER'] = ( | |
| 782 function_header_with_unused_template.substitute(values)) | |
| 783 else: | |
| 784 values['FUNCTION_HEADER'] = function_header_template.substitute(values) | |
| 785 return template.substitute(values) | |
| 786 | |
| 787 def GetKMethodArrayEntry(self, native): | |
| 788 template = Template("""\ | |
| 789 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""") | |
| 790 values = {'NAME': native.name, | |
| 791 'JNI_SIGNATURE': JniSignature(native.params, native.return_type)} | |
| 792 return template.substitute(values) | |
| 793 | |
| 794 def GetUniqueClasses(self, origin): | |
| 795 ret = {self.class_name: self.fully_qualified_class} | |
| 796 for entry in origin: | |
| 797 class_name = self.class_name | |
| 798 jni_class_path = self.fully_qualified_class | |
| 799 if entry.java_class_name: | |
| 800 class_name = entry.java_class_name | |
| 801 jni_class_path = self.fully_qualified_class + '$' + class_name | |
| 802 ret[class_name] = jni_class_path | |
| 803 return ret | |
| 804 | |
| 805 def GetClassPathDefinitions(self): | |
| 806 """Returns the ClassPath constants.""" | |
| 807 ret = [] | |
| 808 template = Template("""\ | |
| 809 const char* const k${JAVA_CLASS}ClassPath = "${JNI_CLASS_PATH}";""") | |
| 810 native_classes = self.GetUniqueClasses(self.natives) | |
| 811 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives) | |
| 812 all_classes = native_classes | |
| 813 all_classes.update(called_by_native_classes) | |
| 814 for clazz in all_classes: | |
| 815 values = { | |
| 816 'JAVA_CLASS': clazz, | |
| 817 'JNI_CLASS_PATH': all_classes[clazz], | |
| 818 } | |
| 819 ret += [template.substitute(values)] | |
| 820 ret += '' | |
| 821 for clazz in called_by_native_classes: | |
| 822 template = Template("""\ | |
| 823 // Leaking this JavaRef as we cannot use LazyInstance from some threads. | |
| 824 base::android::ScopedJavaGlobalRef<jclass>& | |
| 825 g_${JAVA_CLASS}_clazz = *(new base::android::ScopedJavaGlobalRef<jclass>()); """) | |
|
joth
2012/02/14 00:46:49
oh I'd never noticed we changed to using ScopedJav
bulach
2012/02/14 02:12:32
good point! let me see check how complicated this
| |
| 826 values = { | |
| 827 'JAVA_CLASS': clazz, | |
| 828 } | |
| 829 ret += [template.substitute(values)] | |
| 830 return '\n'.join(ret) | |
| 831 | |
| 832 def GetFindClasses(self): | |
| 833 """Returns the imlementation of FindClass for all known classes.""" | |
| 834 template = Template("""\ | |
| 835 g_${JAVA_CLASS}_clazz.Reset(base::android::GetClass(env, k${JAVA_CLASS}ClassPa th));""") | |
| 836 ret = [] | |
| 837 for clazz in self.GetUniqueClasses(self.called_by_natives): | |
| 838 values = {'JAVA_CLASS': clazz} | |
| 839 ret += [template.substitute(values)] | |
| 840 return '\n'.join(ret) | |
| 841 | |
| 842 def GetMethodIDImpl(self, called_by_native): | |
| 843 """Returns the implementation of GetMethodID.""" | |
| 844 template = Template("""\ | |
| 845 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = base::android::Get${STATIC}MethodID( | |
| 846 env, g_${JAVA_CLASS}_clazz, | |
| 847 "${NAME}", | |
| 848 ${JNI_SIGNATURE}); | |
| 849 """) | |
| 850 values = { | |
| 851 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, | |
| 852 'NAME': called_by_native.name, | |
| 853 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, | |
| 854 'STATIC': 'Static' if called_by_native.static else '', | |
| 855 'JNI_SIGNATURE': JniSignature(called_by_native.params, | |
| 856 called_by_native.return_type) | |
| 857 } | |
| 858 return template.substitute(values) | |
| 859 | |
| 860 | |
| 861 def GenerateJNIHeaders(input_files, output_files, use_javap, namespace): | |
| 862 for i in xrange(len(input_files)): | |
| 863 try: | |
| 864 if use_javap: | |
| 865 jni_from_javap = JNIFromJavaP.CreateFromClass(input_files[i], namespace) | |
| 866 output = jni_from_javap.GetContent() | |
| 867 else: | |
| 868 jni_from_java_source = JNIFromJavaSource.CreateFromFile(input_files[i]) | |
| 869 output = jni_from_java_source.GetContent() | |
| 870 except ParseError, e: | |
| 871 print e | |
| 872 sys.exit(1) | |
| 873 if output_files: | |
| 874 header_name = output_files[i] | |
| 875 if not os.path.exists(os.path.dirname(os.path.abspath(header_name))): | |
| 876 os.makedirs(os.path.dirname(os.path.abspath(header_name))) | |
| 877 if (not os.path.exists(header_name) or | |
| 878 file(header_name).read() != output): | |
| 879 print 'Generating ', header_name | |
| 880 output_file = file(header_name, 'w') | |
| 881 output_file.write(output) | |
| 882 output_file.close() | |
| 883 else: | |
| 884 print output | |
| 885 | |
| 886 | |
| 887 def CheckFilenames(input_files, output_files): | |
| 888 """Make sure the input and output have consistent names.""" | |
| 889 if len(input_files) != len(output_files): | |
| 890 sys.exit('Input files length %d must match output length %d' % | |
| 891 (len(input_files), len(output_files))) | |
| 892 for i in xrange(len(input_files)): | |
| 893 input_prefix = os.path.splitext(os.path.basename(input_files[i]))[0] | |
| 894 output_prefix = os.path.splitext(os.path.basename(output_files[i]))[0] | |
| 895 if input_prefix.lower() + 'jni' != output_prefix.replace('_', '').lower(): | |
| 896 sys.exit('\n'.join([ | |
| 897 '*** Error ***', | |
| 898 'Input and output files have inconsistent names:', | |
| 899 '\t' + os.path.basename(input_files[i]), | |
| 900 '\t' + os.path.basename(output_files[i]), | |
| 901 '', | |
| 902 'Input "FooBar.java" must be converted to output "foo_bar_jni.h"', | |
| 903 '', | |
| 904 ])) | |
| 905 | |
| 906 | |
| 907 def main(argv): | |
| 908 usage = """usage: %prog [OPTION] file1[ file2...] [output1[ output2...]] | |
| 909 This script will parse the given java source code extracting the native | |
| 910 declarations and print the header file to stdout (or a file). | |
| 911 See SampleForTests.java for more details. | |
| 912 """ | |
| 913 option_parser = optparse.OptionParser(usage=usage) | |
| 914 option_parser.add_option('-o', dest='output_files', | |
| 915 action='store_true', | |
| 916 default=False, | |
| 917 help='Saves the output to file(s) (the first half of' | |
| 918 ' args specify the java input files, the second' | |
| 919 ' half specify the header output files.') | |
| 920 option_parser.add_option('-p', dest='javap_class', | |
| 921 action='store_true', | |
| 922 default=False, | |
| 923 help='Uses javap to extract the methods from a' | |
| 924 ' pre-compiled class. Input files should point' | |
| 925 ' to pre-compiled Java .class files.') | |
| 926 option_parser.add_option('-n', dest='namespace', | |
| 927 help='Uses as a namespace in the generated header,' | |
| 928 ' instead of the javap class name.') | |
| 929 options, args = option_parser.parse_args(argv) | |
| 930 input_files = args[1:] | |
| 931 output_files = [] | |
| 932 if options.output_files: | |
| 933 output_files = input_files[len(input_files) / 2:] | |
| 934 input_files = input_files[:len(input_files) / 2] | |
| 935 CheckFilenames(input_files, output_files) | |
| 936 GenerateJNIHeaders(input_files, output_files, options.javap_class, | |
| 937 options.namespace) | |
| 938 | |
| 939 | |
| 940 if __name__ == '__main__': | |
| 941 sys.exit(main(sys.argv)) | |
| OLD | NEW |