Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(199)

Side by Side Diff: base/android/jni_generator/jni_generator.py

Issue 9384011: Chrome on Android: adds jni_generator. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Patch Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698