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