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

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

Powered by Google App Engine
This is Rietveld 408576698