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 |