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

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: Joth's comments 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
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))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698