[SyncFS] Build indexes from FileTracker entries on disk.
[chromium-blink-merge.git] / base / android / jni_generator / jni_generator.py
blob56db13cf9182ec006ed1d19e344ad3b6f2f91b32
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Extracts native methods from a Java file and generates the JNI bindings.
7 If you change this, please run and update the tests."""
9 import collections
10 import errno
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 import zipfile
21 CHROMIUM_SRC = os.path.join(
22 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
23 BUILD_ANDROID_GYP = os.path.join(
24 CHROMIUM_SRC, 'build', 'android', 'gyp')
26 sys.path.append(BUILD_ANDROID_GYP)
28 from util import build_utils
31 class ParseError(Exception):
32 """Exception thrown when we can't parse the input file."""
34 def __init__(self, description, *context_lines):
35 Exception.__init__(self)
36 self.description = description
37 self.context_lines = context_lines
39 def __str__(self):
40 context = '\n'.join(self.context_lines)
41 return '***\nERROR: %s\n\n%s\n***' % (self.description, context)
44 class Param(object):
45 """Describes a param for a method, either java or native."""
47 def __init__(self, **kwargs):
48 self.datatype = kwargs['datatype']
49 self.name = kwargs['name']
52 class NativeMethod(object):
53 """Describes a C/C++ method that is called by Java code"""
55 def __init__(self, **kwargs):
56 self.static = kwargs['static']
57 self.java_class_name = kwargs['java_class_name']
58 self.return_type = kwargs['return_type']
59 self.name = kwargs['name']
60 self.params = kwargs['params']
61 if self.params:
62 assert type(self.params) is list
63 assert type(self.params[0]) is Param
64 if (self.params and
65 self.params[0].datatype == kwargs.get('ptr_type', 'int') and
66 self.params[0].name.startswith('native')):
67 self.type = 'method'
68 self.p0_type = self.params[0].name[len('native'):]
69 if kwargs.get('native_class_name'):
70 self.p0_type = kwargs['native_class_name']
71 else:
72 self.type = 'function'
73 self.method_id_var_name = kwargs.get('method_id_var_name', None)
76 class CalledByNative(object):
77 """Describes a java method exported to c/c++"""
79 def __init__(self, **kwargs):
80 self.system_class = kwargs['system_class']
81 self.unchecked = kwargs['unchecked']
82 self.static = kwargs['static']
83 self.java_class_name = kwargs['java_class_name']
84 self.return_type = kwargs['return_type']
85 self.name = kwargs['name']
86 self.params = kwargs['params']
87 self.method_id_var_name = kwargs.get('method_id_var_name', None)
88 self.signature = kwargs.get('signature')
89 self.is_constructor = kwargs.get('is_constructor', False)
90 self.env_call = GetEnvCall(self.is_constructor, self.static,
91 self.return_type)
92 self.static_cast = GetStaticCastForReturnType(self.return_type)
95 class ConstantField(object):
96 def __init__(self, **kwargs):
97 self.name = kwargs['name']
98 self.value = kwargs['value']
101 def JavaDataTypeToC(java_type):
102 """Returns a C datatype for the given java type."""
103 java_pod_type_map = {
104 'int': 'jint',
105 'byte': 'jbyte',
106 'char': 'jchar',
107 'short': 'jshort',
108 'boolean': 'jboolean',
109 'long': 'jlong',
110 'double': 'jdouble',
111 'float': 'jfloat',
113 java_type_map = {
114 'void': 'void',
115 'String': 'jstring',
116 'java/lang/String': 'jstring',
117 'java/lang/Class': 'jclass',
120 if java_type in java_pod_type_map:
121 return java_pod_type_map[java_type]
122 elif java_type in java_type_map:
123 return java_type_map[java_type]
124 elif java_type.endswith('[]'):
125 if java_type[:-2] in java_pod_type_map:
126 return java_pod_type_map[java_type[:-2]] + 'Array'
127 return 'jobjectArray'
128 elif java_type.startswith('Class'):
129 # Checking just the start of the name, rather than a direct comparison,
130 # in order to handle generics.
131 return 'jclass'
132 else:
133 return 'jobject'
136 def JavaDataTypeToCForCalledByNativeParam(java_type):
137 """Returns a C datatype to be when calling from native."""
138 if java_type == 'int':
139 return 'JniIntWrapper'
140 else:
141 return JavaDataTypeToC(java_type)
144 def JavaReturnValueToC(java_type):
145 """Returns a valid C return value for the given java type."""
146 java_pod_type_map = {
147 'int': '0',
148 'byte': '0',
149 'char': '0',
150 'short': '0',
151 'boolean': 'false',
152 'long': '0',
153 'double': '0',
154 'float': '0',
155 'void': ''
157 return java_pod_type_map.get(java_type, 'NULL')
160 class JniParams(object):
161 _imports = []
162 _fully_qualified_class = ''
163 _package = ''
164 _inner_classes = []
165 _remappings = []
166 _implicit_imports = []
168 @staticmethod
169 def SetFullyQualifiedClass(fully_qualified_class):
170 JniParams._fully_qualified_class = 'L' + fully_qualified_class
171 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1])
173 @staticmethod
174 def AddAdditionalImport(class_name):
175 assert class_name.endswith('.class')
176 raw_class_name = class_name[:-len('.class')]
177 if '.' in raw_class_name:
178 raise SyntaxError('%s cannot be used in @JNIAdditionalImport. '
179 'Only import unqualified outer classes.' % class_name)
180 new_import = 'L%s/%s' % (JniParams._package, raw_class_name)
181 if new_import in JniParams._imports:
182 raise SyntaxError('Do not use JNIAdditionalImport on an already '
183 'imported class: %s' % (new_import.replace('/', '.')))
184 JniParams._imports += [new_import]
186 @staticmethod
187 def ExtractImportsAndInnerClasses(contents):
188 if not JniParams._package:
189 raise RuntimeError('SetFullyQualifiedClass must be called before '
190 'ExtractImportsAndInnerClasses')
191 contents = contents.replace('\n', '')
192 re_import = re.compile(r'import.*?(?P<class>\S*?);')
193 for match in re.finditer(re_import, contents):
194 JniParams._imports += ['L' + match.group('class').replace('.', '/')]
196 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W')
197 for match in re.finditer(re_inner, contents):
198 inner = match.group('name')
199 if not JniParams._fully_qualified_class.endswith(inner):
200 JniParams._inner_classes += [JniParams._fully_qualified_class + '$' +
201 inner]
203 re_additional_imports = re.compile(
204 r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
205 for match in re.finditer(re_additional_imports, contents):
206 for class_name in match.group('class_names').split(','):
207 JniParams.AddAdditionalImport(class_name.strip())
209 @staticmethod
210 def ParseJavaPSignature(signature_line):
211 prefix = 'Signature: '
212 return '"%s"' % signature_line[signature_line.index(prefix) + len(prefix):]
214 @staticmethod
215 def JavaToJni(param):
216 """Converts a java param into a JNI signature type."""
217 pod_param_map = {
218 'int': 'I',
219 'boolean': 'Z',
220 'char': 'C',
221 'short': 'S',
222 'long': 'J',
223 'double': 'D',
224 'float': 'F',
225 'byte': 'B',
226 'void': 'V',
228 object_param_list = [
229 'Ljava/lang/Boolean',
230 'Ljava/lang/Integer',
231 'Ljava/lang/Long',
232 'Ljava/lang/Object',
233 'Ljava/lang/String',
234 'Ljava/lang/Class',
237 prefix = ''
238 # Array?
239 while param[-2:] == '[]':
240 prefix += '['
241 param = param[:-2]
242 # Generic?
243 if '<' in param:
244 param = param[:param.index('<')]
245 if param in pod_param_map:
246 return prefix + pod_param_map[param]
247 if '/' in param:
248 # Coming from javap, use the fully qualified param directly.
249 return prefix + 'L' + JniParams.RemapClassName(param) + ';'
251 for qualified_name in (object_param_list +
252 [JniParams._fully_qualified_class] +
253 JniParams._inner_classes):
254 if (qualified_name.endswith('/' + param) or
255 qualified_name.endswith('$' + param.replace('.', '$')) or
256 qualified_name == 'L' + param):
257 return prefix + JniParams.RemapClassName(qualified_name) + ';'
259 # Is it from an import? (e.g. referecing Class from import pkg.Class;
260 # note that referencing an inner class Inner from import pkg.Class.Inner
261 # is not supported).
262 for qualified_name in JniParams._imports:
263 if qualified_name.endswith('/' + param):
264 # Ensure it's not an inner class.
265 components = qualified_name.split('/')
266 if len(components) > 2 and components[-2][0].isupper():
267 raise SyntaxError('Inner class (%s) can not be imported '
268 'and used by JNI (%s). Please import the outer '
269 'class and use Outer.Inner instead.' %
270 (qualified_name, param))
271 return prefix + JniParams.RemapClassName(qualified_name) + ';'
273 # Is it an inner class from an outer class import? (e.g. referencing
274 # Class.Inner from import pkg.Class).
275 if '.' in param:
276 components = param.split('.')
277 outer = '/'.join(components[:-1])
278 inner = components[-1]
279 for qualified_name in JniParams._imports:
280 if qualified_name.endswith('/' + outer):
281 return (prefix + JniParams.RemapClassName(qualified_name) +
282 '$' + inner + ';')
283 raise SyntaxError('Inner class (%s) can not be '
284 'used directly by JNI. Please import the outer '
285 'class, probably:\n'
286 'import %s.%s;' %
287 (param, JniParams._package.replace('/', '.'),
288 outer.replace('/', '.')))
290 JniParams._CheckImplicitImports(param)
292 # Type not found, falling back to same package as this class.
293 return (prefix + 'L' +
294 JniParams.RemapClassName(JniParams._package + '/' + param) + ';')
296 @staticmethod
297 def _CheckImplicitImports(param):
298 # Ensure implicit imports, such as java.lang.*, are not being treated
299 # as being in the same package.
300 if not JniParams._implicit_imports:
301 # This file was generated from android.jar and lists
302 # all classes that are implicitly imported.
303 with file(os.path.join(os.path.dirname(sys.argv[0]),
304 'android_jar.classes'), 'r') as f:
305 JniParams._implicit_imports = f.readlines()
306 for implicit_import in JniParams._implicit_imports:
307 implicit_import = implicit_import.strip().replace('.class', '')
308 implicit_import = implicit_import.replace('/', '.')
309 if implicit_import.endswith('.' + param):
310 raise SyntaxError('Ambiguous class (%s) can not be used directly '
311 'by JNI.\nPlease import it, probably:\n\n'
312 'import %s;' %
313 (param, implicit_import))
316 @staticmethod
317 def Signature(params, returns, wrap):
318 """Returns the JNI signature for the given datatypes."""
319 items = ['(']
320 items += [JniParams.JavaToJni(param.datatype) for param in params]
321 items += [')']
322 items += [JniParams.JavaToJni(returns)]
323 if wrap:
324 return '\n' + '\n'.join(['"' + item + '"' for item in items])
325 else:
326 return '"' + ''.join(items) + '"'
328 @staticmethod
329 def Parse(params):
330 """Parses the params into a list of Param objects."""
331 if not params:
332 return []
333 ret = []
334 for p in [p.strip() for p in params.split(',')]:
335 items = p.split(' ')
336 if 'final' in items:
337 items.remove('final')
338 param = Param(
339 datatype=items[0],
340 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
342 ret += [param]
343 return ret
345 @staticmethod
346 def RemapClassName(class_name):
347 """Remaps class names using the jarjar mapping table."""
348 for old, new in JniParams._remappings:
349 if old.endswith('**') and old[:-2] in class_name:
350 return class_name.replace(old[:-2], new, 1)
351 if '*' not in old and class_name.endswith(old):
352 return class_name.replace(old, new, 1)
354 return class_name
356 @staticmethod
357 def SetJarJarMappings(mappings):
358 """Parse jarjar mappings from a string."""
359 JniParams._remappings = []
360 for line in mappings.splitlines():
361 rule = line.split()
362 if rule[0] != 'rule':
363 continue
364 _, src, dest = rule
365 src = src.replace('.', '/')
366 dest = dest.replace('.', '/')
367 if src.endswith('**'):
368 src_real_name = src[:-2]
369 else:
370 assert not '*' in src
371 src_real_name = src
373 if dest.endswith('@0'):
374 JniParams._remappings.append((src, dest[:-2] + src_real_name))
375 elif dest.endswith('@1'):
376 assert '**' in src
377 JniParams._remappings.append((src, dest[:-2]))
378 else:
379 assert not '@' in dest
380 JniParams._remappings.append((src, dest))
383 def ExtractJNINamespace(contents):
384 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
385 m = re.findall(re_jni_namespace, contents)
386 if not m:
387 return ''
388 return m[0]
391 def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
392 re_package = re.compile('.*?package (.*?);')
393 matches = re.findall(re_package, contents)
394 if not matches:
395 raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
396 return (matches[0].replace('.', '/') + '/' +
397 os.path.splitext(os.path.basename(java_file_name))[0])
400 def ExtractNatives(contents, ptr_type):
401 """Returns a list of dict containing information about a native method."""
402 contents = contents.replace('\n', '')
403 natives = []
404 re_native = re.compile(r'(@NativeClassQualifiedName'
405 '\(\"(?P<native_class_name>.*?)\"\)\s+)?'
406 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
407 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
408 '(?P<return_type>\S*) '
409 '(?P<name>native\w+)\((?P<params>.*?)\);')
410 for match in re.finditer(re_native, contents):
411 native = NativeMethod(
412 static='static' in match.group('qualifiers'),
413 java_class_name=match.group('java_class_name'),
414 native_class_name=match.group('native_class_name'),
415 return_type=match.group('return_type'),
416 name=match.group('name').replace('native', ''),
417 params=JniParams.Parse(match.group('params')),
418 ptr_type=ptr_type)
419 natives += [native]
420 return natives
423 def GetStaticCastForReturnType(return_type):
424 type_map = { 'String' : 'jstring',
425 'java/lang/String' : 'jstring',
426 'boolean[]': 'jbooleanArray',
427 'byte[]': 'jbyteArray',
428 'char[]': 'jcharArray',
429 'short[]': 'jshortArray',
430 'int[]': 'jintArray',
431 'long[]': 'jlongArray',
432 'float[]': 'jfloatArray',
433 'double[]': 'jdoubleArray' }
434 ret = type_map.get(return_type, None)
435 if ret:
436 return ret
437 if return_type.endswith('[]'):
438 return 'jobjectArray'
439 return None
442 def GetEnvCall(is_constructor, is_static, return_type):
443 """Maps the types availabe via env->Call__Method."""
444 if is_constructor:
445 return 'NewObject'
446 env_call_map = {'boolean': 'Boolean',
447 'byte': 'Byte',
448 'char': 'Char',
449 'short': 'Short',
450 'int': 'Int',
451 'long': 'Long',
452 'float': 'Float',
453 'void': 'Void',
454 'double': 'Double',
455 'Object': 'Object',
457 call = env_call_map.get(return_type, 'Object')
458 if is_static:
459 call = 'Static' + call
460 return 'Call' + call + 'Method'
463 def GetMangledParam(datatype):
464 """Returns a mangled identifier for the datatype."""
465 if len(datatype) <= 2:
466 return datatype.replace('[', 'A')
467 ret = ''
468 for i in range(1, len(datatype)):
469 c = datatype[i]
470 if c == '[':
471 ret += 'A'
472 elif c.isupper() or datatype[i - 1] in ['/', 'L']:
473 ret += c.upper()
474 return ret
477 def GetMangledMethodName(name, params, return_type):
478 """Returns a mangled method name for the given signature.
480 The returned name can be used as a C identifier and will be unique for all
481 valid overloads of the same method.
483 Args:
484 name: string.
485 params: list of Param.
486 return_type: string.
488 Returns:
489 A mangled name.
491 mangled_items = []
492 for datatype in [return_type] + [x.datatype for x in params]:
493 mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))]
494 mangled_name = name + '_'.join(mangled_items)
495 assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
496 return mangled_name
499 def MangleCalledByNatives(called_by_natives):
500 """Mangles all the overloads from the call_by_natives list."""
501 method_counts = collections.defaultdict(
502 lambda: collections.defaultdict(lambda: 0))
503 for called_by_native in called_by_natives:
504 java_class_name = called_by_native.java_class_name
505 name = called_by_native.name
506 method_counts[java_class_name][name] += 1
507 for called_by_native in called_by_natives:
508 java_class_name = called_by_native.java_class_name
509 method_name = called_by_native.name
510 method_id_var_name = method_name
511 if method_counts[java_class_name][method_name] > 1:
512 method_id_var_name = GetMangledMethodName(method_name,
513 called_by_native.params,
514 called_by_native.return_type)
515 called_by_native.method_id_var_name = method_id_var_name
516 return called_by_natives
519 # Regex to match the JNI return types that should be included in a
520 # ScopedJavaLocalRef.
521 RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array')
523 # Regex to match a string like "@CalledByNative public void foo(int bar)".
524 RE_CALLED_BY_NATIVE = re.compile(
525 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
526 '\s+(?P<prefix>[\w ]*?)'
527 '\s*(?P<return_type>\S+?)'
528 '\s+(?P<name>\w+)'
529 '\s*\((?P<params>[^\)]*)\)')
532 def ExtractCalledByNatives(contents):
533 """Parses all methods annotated with @CalledByNative.
535 Args:
536 contents: the contents of the java file.
538 Returns:
539 A list of dict with information about the annotated methods.
540 TODO(bulach): return a CalledByNative object.
542 Raises:
543 ParseError: if unable to parse.
545 called_by_natives = []
546 for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
547 called_by_natives += [CalledByNative(
548 system_class=False,
549 unchecked='Unchecked' in match.group('Unchecked'),
550 static='static' in match.group('prefix'),
551 java_class_name=match.group('annotation') or '',
552 return_type=match.group('return_type'),
553 name=match.group('name'),
554 params=JniParams.Parse(match.group('params')))]
555 # Check for any @CalledByNative occurrences that weren't matched.
556 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
557 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
558 if '@CalledByNative' in line1:
559 raise ParseError('could not parse @CalledByNative method signature',
560 line1, line2)
561 return MangleCalledByNatives(called_by_natives)
564 class JNIFromJavaP(object):
565 """Uses 'javap' to parse a .class file and generate the JNI header file."""
567 def __init__(self, contents, options):
568 self.contents = contents
569 self.namespace = options.namespace
570 for line in contents:
571 class_name = re.match(
572 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
573 line)
574 if class_name:
575 self.fully_qualified_class = class_name.group('class_name')
576 break
577 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
578 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
579 # away the <...> and use the raw class name that Java 6 would've given us.
580 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
581 JniParams.SetFullyQualifiedClass(self.fully_qualified_class)
582 self.java_class_name = self.fully_qualified_class.split('/')[-1]
583 if not self.namespace:
584 self.namespace = 'JNI_' + self.java_class_name
585 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
586 '\((?P<params>.*?)\)')
587 self.called_by_natives = []
588 for lineno, content in enumerate(contents[2:], 2):
589 match = re.match(re_method, content)
590 if not match:
591 continue
592 self.called_by_natives += [CalledByNative(
593 system_class=True,
594 unchecked=False,
595 static='static' in match.group('prefix'),
596 java_class_name='',
597 return_type=match.group('return_type').replace('.', '/'),
598 name=match.group('name'),
599 params=JniParams.Parse(match.group('params').replace('.', '/')),
600 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
601 re_constructor = re.compile('(.*?)public ' +
602 self.fully_qualified_class.replace('/', '.') +
603 '\((?P<params>.*?)\)')
604 for lineno, content in enumerate(contents[2:], 2):
605 match = re.match(re_constructor, content)
606 if not match:
607 continue
608 self.called_by_natives += [CalledByNative(
609 system_class=True,
610 unchecked=False,
611 static=False,
612 java_class_name='',
613 return_type=self.fully_qualified_class,
614 name='Constructor',
615 params=JniParams.Parse(match.group('params').replace('.', '/')),
616 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
617 is_constructor=True)]
618 self.called_by_natives = MangleCalledByNatives(self.called_by_natives)
620 self.constant_fields = []
621 re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
622 re_constant_field_value = re.compile(
623 '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)')
624 for lineno, content in enumerate(contents[2:], 2):
625 match = re.match(re_constant_field, content)
626 if not match:
627 continue
628 value = re.match(re_constant_field_value, contents[lineno + 2])
629 if not value:
630 value = re.match(re_constant_field_value, contents[lineno + 3])
631 if value:
632 self.constant_fields.append(
633 ConstantField(name=match.group('name'),
634 value=value.group('value')))
636 self.inl_header_file_generator = InlHeaderFileGenerator(
637 self.namespace, self.fully_qualified_class, [],
638 self.called_by_natives, self.constant_fields, options)
640 def GetContent(self):
641 return self.inl_header_file_generator.GetContent()
643 @staticmethod
644 def CreateFromClass(class_file, options):
645 class_name = os.path.splitext(os.path.basename(class_file))[0]
646 p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
647 '-s', class_name],
648 cwd=os.path.dirname(class_file),
649 stdout=subprocess.PIPE,
650 stderr=subprocess.PIPE)
651 stdout, _ = p.communicate()
652 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
653 return jni_from_javap
656 class JNIFromJavaSource(object):
657 """Uses the given java source file to generate the JNI header file."""
659 # Match single line comments, multiline comments, character literals, and
660 # double-quoted strings.
661 _comment_remover_regex = re.compile(
662 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
663 re.DOTALL | re.MULTILINE)
665 def __init__(self, contents, fully_qualified_class, options):
666 contents = self._RemoveComments(contents)
667 JniParams.SetFullyQualifiedClass(fully_qualified_class)
668 JniParams.ExtractImportsAndInnerClasses(contents)
669 jni_namespace = ExtractJNINamespace(contents) or options.namespace
670 natives = ExtractNatives(contents, options.ptr_type)
671 called_by_natives = ExtractCalledByNatives(contents)
672 if len(natives) == 0 and len(called_by_natives) == 0:
673 raise SyntaxError('Unable to find any JNI methods for %s.' %
674 fully_qualified_class)
675 inl_header_file_generator = InlHeaderFileGenerator(
676 jni_namespace, fully_qualified_class, natives, called_by_natives,
677 [], options)
678 self.content = inl_header_file_generator.GetContent()
680 @classmethod
681 def _RemoveComments(cls, contents):
682 # We need to support both inline and block comments, and we need to handle
683 # strings that contain '//' or '/*'.
684 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
685 # parser. Maybe we could ditch JNIFromJavaSource and just always use
686 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
687 # http://code.google.com/p/chromium/issues/detail?id=138941
688 def replacer(match):
689 # Replace matches that are comments with nothing; return literals/strings
690 # unchanged.
691 s = match.group(0)
692 if s.startswith('/'):
693 return ''
694 else:
695 return s
696 return cls._comment_remover_regex.sub(replacer, contents)
698 def GetContent(self):
699 return self.content
701 @staticmethod
702 def CreateFromFile(java_file_name, options):
703 contents = file(java_file_name).read()
704 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
705 contents)
706 return JNIFromJavaSource(contents, fully_qualified_class, options)
709 class InlHeaderFileGenerator(object):
710 """Generates an inline header file for JNI integration."""
712 def __init__(self, namespace, fully_qualified_class, natives,
713 called_by_natives, constant_fields, options):
714 self.namespace = namespace
715 self.fully_qualified_class = fully_qualified_class
716 self.class_name = self.fully_qualified_class.split('/')[-1]
717 self.natives = natives
718 self.called_by_natives = called_by_natives
719 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
720 self.constant_fields = constant_fields
721 self.options = options
722 self.init_native = self.ExtractInitNative(options)
724 def ExtractInitNative(self, options):
725 for native in self.natives:
726 if options.jni_init_native_name == 'native' + native.name:
727 self.natives.remove(native)
728 return native
729 return None
731 def GetContent(self):
732 """Returns the content of the JNI binding file."""
733 template = Template("""\
734 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
735 // Use of this source code is governed by a BSD-style license that can be
736 // found in the LICENSE file.
739 // This file is autogenerated by
740 // ${SCRIPT_NAME}
741 // For
742 // ${FULLY_QUALIFIED_CLASS}
744 #ifndef ${HEADER_GUARD}
745 #define ${HEADER_GUARD}
747 #include <jni.h>
749 ${INCLUDES}
751 #include "base/android/jni_int_wrapper.h"
753 // Step 1: forward declarations.
754 namespace {
755 $CLASS_PATH_DEFINITIONS
756 $METHOD_ID_DEFINITIONS
757 } // namespace
759 $OPEN_NAMESPACE
760 $FORWARD_DECLARATIONS
762 $CONSTANT_FIELDS
764 // Step 2: method stubs.
765 $METHOD_STUBS
767 // Step 3: RegisterNatives.
768 $JNI_NATIVE_METHODS
769 $REGISTER_NATIVES
770 $CLOSE_NAMESPACE
771 $JNI_REGISTER_NATIVES
772 #endif // ${HEADER_GUARD}
773 """)
774 values = {
775 'SCRIPT_NAME': self.options.script_name,
776 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
777 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
778 'METHOD_ID_DEFINITIONS': self.GetMethodIDDefinitionsString(),
779 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(),
780 'CONSTANT_FIELDS': self.GetConstantFieldsString(),
781 'METHOD_STUBS': self.GetMethodStubsString(),
782 'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
783 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(),
784 'REGISTER_NATIVES': self.GetRegisterNativesString(),
785 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
786 'HEADER_GUARD': self.header_guard,
787 'INCLUDES': self.GetIncludesString(),
788 'JNI_REGISTER_NATIVES': self.GetJNIRegisterNativesString()
790 return WrapOutput(template.substitute(values))
792 def GetClassPathDefinitionsString(self):
793 ret = []
794 ret += [self.GetClassPathDefinitions()]
795 return '\n'.join(ret)
797 def GetMethodIDDefinitionsString(self):
798 """Returns the definition of method ids for the called by native methods."""
799 if not self.options.eager_called_by_natives:
800 return ''
801 template = Template("""\
802 jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = NULL;""")
803 ret = []
804 for called_by_native in self.called_by_natives:
805 values = {
806 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
807 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
809 ret += [template.substitute(values)]
810 return '\n'.join(ret)
812 def GetForwardDeclarationsString(self):
813 ret = []
814 for native in self.natives:
815 if native.type != 'method':
816 ret += [self.GetForwardDeclaration(native)]
817 return '\n'.join(ret)
819 def GetConstantFieldsString(self):
820 if not self.constant_fields:
821 return ''
822 ret = ['enum Java_%s_constant_fields {' % self.class_name]
823 for c in self.constant_fields:
824 ret += [' %s = %s,' % (c.name, c.value)]
825 ret += ['};']
826 return '\n'.join(ret)
828 def GetMethodStubsString(self):
829 """Returns the code corresponding to method stubs."""
830 ret = []
831 for native in self.natives:
832 if native.type == 'method':
833 ret += [self.GetNativeMethodStubString(native)]
834 if self.options.eager_called_by_natives:
835 ret += self.GetEagerCalledByNativeMethodStubs()
836 else:
837 ret += self.GetLazyCalledByNativeMethodStubs()
838 return '\n'.join(ret)
840 def GetLazyCalledByNativeMethodStubs(self):
841 return [self.GetLazyCalledByNativeMethodStub(called_by_native)
842 for called_by_native in self.called_by_natives]
844 def GetEagerCalledByNativeMethodStubs(self):
845 ret = []
846 if self.called_by_natives:
847 ret += ['namespace {']
848 for called_by_native in self.called_by_natives:
849 ret += [self.GetEagerCalledByNativeMethodStub(called_by_native)]
850 ret += ['} // namespace']
851 return ret
853 def GetIncludesString(self):
854 if not self.options.includes:
855 return ''
856 includes = self.options.includes.split(',')
857 return '\n'.join('#include "%s"' % x for x in includes)
859 def GetKMethodsString(self, clazz):
860 ret = []
861 for native in self.natives:
862 if (native.java_class_name == clazz or
863 (not native.java_class_name and clazz == self.class_name)):
864 ret += [self.GetKMethodArrayEntry(native)]
865 return '\n'.join(ret)
867 def SubstituteNativeMethods(self, template):
868 """Substitutes JAVA_CLASS and KMETHODS in the provided template."""
869 ret = []
870 all_classes = self.GetUniqueClasses(self.natives)
871 all_classes[self.class_name] = self.fully_qualified_class
872 for clazz in all_classes:
873 kmethods = self.GetKMethodsString(clazz)
874 if kmethods:
875 values = {'JAVA_CLASS': clazz,
876 'KMETHODS': kmethods}
877 ret += [template.substitute(values)]
878 if not ret: return ''
879 return '\n' + '\n'.join(ret)
881 def GetJNINativeMethodsString(self):
882 """Returns the implementation of the array of native methods."""
883 template = Template("""\
884 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
885 ${KMETHODS}
887 """)
888 return self.SubstituteNativeMethods(template)
890 def GetRegisterCalledByNativesImplString(self):
891 """Returns the code for registering the called by native methods."""
892 if not self.options.eager_called_by_natives:
893 return ''
894 template = Template("""\
895 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = ${GET_METHOD_ID_IMPL}
896 if (g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} == NULL) {
897 return false;
899 """)
900 ret = []
901 for called_by_native in self.called_by_natives:
902 values = {
903 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
904 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
905 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native),
907 ret += [template.substitute(values)]
908 return '\n'.join(ret)
910 def GetRegisterNativesString(self):
911 """Returns the code for RegisterNatives."""
912 template = Template("""\
913 ${REGISTER_NATIVES_SIGNATURE} {
914 ${CLASSES}
915 ${NATIVES}
916 ${CALLED_BY_NATIVES}
917 return true;
919 """)
920 signature = 'static bool RegisterNativesImpl(JNIEnv* env'
921 if self.init_native:
922 signature += ', jclass clazz)'
923 else:
924 signature += ')'
926 natives = self.GetRegisterNativesImplString()
927 called_by_natives = self.GetRegisterCalledByNativesImplString()
928 values = {'REGISTER_NATIVES_SIGNATURE': signature,
929 'CLASSES': self.GetFindClasses(),
930 'NATIVES': natives,
931 'CALLED_BY_NATIVES': called_by_natives,
933 return template.substitute(values)
935 def GetRegisterNativesImplString(self):
936 """Returns the shared implementation for RegisterNatives."""
937 template = Template("""\
938 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
940 if (env->RegisterNatives(g_${JAVA_CLASS}_clazz,
941 kMethods${JAVA_CLASS},
942 kMethods${JAVA_CLASS}Size) < 0) {
943 jni_generator::HandleRegistrationError(
944 env, g_${JAVA_CLASS}_clazz, __FILE__);
945 return false;
947 """)
948 return self.SubstituteNativeMethods(template)
950 def GetJNIRegisterNativesString(self):
951 """Returns the implementation for the JNI registration of native methods."""
952 if not self.init_native:
953 return ''
955 template = Template("""\
956 extern "C" JNIEXPORT bool JNICALL
957 Java_${FULLY_QUALIFIED_CLASS}_${INIT_NATIVE_NAME}(JNIEnv* env, jclass clazz) {
958 return ${NAMESPACE}RegisterNativesImpl(env, clazz);
960 """)
961 fully_qualified_class = self.fully_qualified_class.replace('/', '_')
962 namespace = ''
963 if self.namespace:
964 namespace = self.namespace + '::'
965 values = {'FULLY_QUALIFIED_CLASS': fully_qualified_class,
966 'INIT_NATIVE_NAME': 'native' + self.init_native.name,
967 'NAMESPACE': namespace,
968 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString()
970 return template.substitute(values)
972 def GetOpenNamespaceString(self):
973 if self.namespace:
974 all_namespaces = ['namespace %s {' % ns
975 for ns in self.namespace.split('::')]
976 return '\n'.join(all_namespaces)
977 return ''
979 def GetCloseNamespaceString(self):
980 if self.namespace:
981 all_namespaces = ['} // namespace %s' % ns
982 for ns in self.namespace.split('::')]
983 all_namespaces.reverse()
984 return '\n'.join(all_namespaces) + '\n'
985 return ''
987 def GetJNIFirstParam(self, native):
988 ret = []
989 if native.type == 'method':
990 ret = ['jobject jcaller']
991 elif native.type == 'function':
992 if native.static:
993 ret = ['jclass jcaller']
994 else:
995 ret = ['jobject jcaller']
996 return ret
998 def GetParamsInDeclaration(self, native):
999 """Returns the params for the stub declaration.
1001 Args:
1002 native: the native dictionary describing the method.
1004 Returns:
1005 A string containing the params.
1007 return ',\n '.join(self.GetJNIFirstParam(native) +
1008 [JavaDataTypeToC(param.datatype) + ' ' +
1009 param.name
1010 for param in native.params])
1012 def GetCalledByNativeParamsInDeclaration(self, called_by_native):
1013 return ',\n '.join([
1014 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
1015 param.name
1016 for param in called_by_native.params])
1018 def GetForwardDeclaration(self, native):
1019 template = Template("""
1020 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
1021 """)
1022 values = {'RETURN': JavaDataTypeToC(native.return_type),
1023 'NAME': native.name,
1024 'PARAMS': self.GetParamsInDeclaration(native)}
1025 return template.substitute(values)
1027 def GetNativeMethodStubString(self, native):
1028 """Returns stubs for native methods."""
1029 template = Template("""\
1030 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {
1031 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
1032 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
1033 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
1035 """)
1036 params = []
1037 if not self.options.pure_native_methods:
1038 params = ['env', 'jcaller']
1039 params_in_call = ', '.join(params + [p.name for p in native.params[1:]])
1041 return_type = JavaDataTypeToC(native.return_type)
1042 optional_error_return = JavaReturnValueToC(native.return_type)
1043 if optional_error_return:
1044 optional_error_return = ', ' + optional_error_return
1045 post_call = ''
1046 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1047 post_call = '.Release()'
1048 values = {
1049 'RETURN': return_type,
1050 'OPTIONAL_ERROR_RETURN': optional_error_return,
1051 'NAME': native.name,
1052 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native),
1053 'PARAM0_NAME': native.params[0].name,
1054 'P0_TYPE': native.p0_type,
1055 'PARAMS_IN_CALL': params_in_call,
1056 'POST_CALL': post_call
1058 return template.substitute(values)
1060 def GetArgument(self, param):
1061 return ('as_jint(' + param.name + ')'
1062 if param.datatype == 'int' else param.name)
1064 def GetArgumentsInCall(self, params):
1065 """Return a string of arguments to call from native into Java"""
1066 return [self.GetArgument(p) for p in params]
1068 def GetCalledByNativeValues(self, called_by_native):
1069 """Fills in necessary values for the CalledByNative methods."""
1070 if called_by_native.static or called_by_native.is_constructor:
1071 first_param_in_declaration = ''
1072 first_param_in_call = ('g_%s_clazz' %
1073 (called_by_native.java_class_name or
1074 self.class_name))
1075 else:
1076 first_param_in_declaration = ', jobject obj'
1077 first_param_in_call = 'obj'
1078 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
1079 called_by_native)
1080 if params_in_declaration:
1081 params_in_declaration = ', ' + params_in_declaration
1082 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
1083 if params_in_call:
1084 params_in_call = ', ' + params_in_call
1085 pre_call = ''
1086 post_call = ''
1087 if called_by_native.static_cast:
1088 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
1089 post_call = ')'
1090 check_exception = ''
1091 if not called_by_native.unchecked:
1092 check_exception = 'jni_generator::CheckException(env);'
1093 return_type = JavaDataTypeToC(called_by_native.return_type)
1094 optional_error_return = JavaReturnValueToC(called_by_native.return_type)
1095 if optional_error_return:
1096 optional_error_return = ', ' + optional_error_return
1097 return_declaration = ''
1098 return_clause = ''
1099 if return_type != 'void':
1100 pre_call = ' ' + pre_call
1101 return_declaration = return_type + ' ret ='
1102 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1103 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
1104 return_clause = 'return ' + return_type + '(env, ret);'
1105 else:
1106 return_clause = 'return ret;'
1107 return {
1108 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
1109 'RETURN_TYPE': return_type,
1110 'OPTIONAL_ERROR_RETURN': optional_error_return,
1111 'RETURN_DECLARATION': return_declaration,
1112 'RETURN_CLAUSE': return_clause,
1113 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
1114 'PARAMS_IN_DECLARATION': params_in_declaration,
1115 'PRE_CALL': pre_call,
1116 'POST_CALL': post_call,
1117 'ENV_CALL': called_by_native.env_call,
1118 'FIRST_PARAM_IN_CALL': first_param_in_call,
1119 'PARAMS_IN_CALL': params_in_call,
1120 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1121 'CHECK_EXCEPTION': check_exception,
1122 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native)
1125 def GetEagerCalledByNativeMethodStub(self, called_by_native):
1126 """Returns the implementation of the called by native method."""
1127 template = Template("""
1128 static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\
1129 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION}) {
1130 ${RETURN_DECLARATION}${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1131 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL};
1132 ${RETURN_CLAUSE}
1133 }""")
1134 values = self.GetCalledByNativeValues(called_by_native)
1135 return template.substitute(values)
1137 def GetLazyCalledByNativeMethodStub(self, called_by_native):
1138 """Returns a string."""
1139 function_signature_template = Template("""\
1140 static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
1141 JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
1142 function_header_template = Template("""\
1143 ${FUNCTION_SIGNATURE} {""")
1144 function_header_with_unused_template = Template("""\
1145 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
1146 ${FUNCTION_SIGNATURE} {""")
1147 template = Template("""
1148 static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
1149 ${FUNCTION_HEADER}
1150 /* Must call RegisterNativesImpl() */
1151 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
1152 g_${JAVA_CLASS}_clazz${OPTIONAL_ERROR_RETURN});
1153 jmethodID method_id =
1154 ${GET_METHOD_ID_IMPL}
1155 ${RETURN_DECLARATION}
1156 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1157 method_id${PARAMS_IN_CALL})${POST_CALL};
1158 ${CHECK_EXCEPTION}
1159 ${RETURN_CLAUSE}
1160 }""")
1161 values = self.GetCalledByNativeValues(called_by_native)
1162 values['FUNCTION_SIGNATURE'] = (
1163 function_signature_template.substitute(values))
1164 if called_by_native.system_class:
1165 values['FUNCTION_HEADER'] = (
1166 function_header_with_unused_template.substitute(values))
1167 else:
1168 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
1169 return template.substitute(values)
1171 def GetKMethodArrayEntry(self, native):
1172 template = Template("""\
1173 { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""")
1174 values = {'NAME': native.name,
1175 'JNI_SIGNATURE': JniParams.Signature(native.params,
1176 native.return_type,
1177 True)}
1178 return template.substitute(values)
1180 def GetUniqueClasses(self, origin):
1181 ret = {self.class_name: self.fully_qualified_class}
1182 for entry in origin:
1183 class_name = self.class_name
1184 jni_class_path = self.fully_qualified_class
1185 if entry.java_class_name:
1186 class_name = entry.java_class_name
1187 jni_class_path = self.fully_qualified_class + '$' + class_name
1188 ret[class_name] = jni_class_path
1189 return ret
1191 def GetClassPathDefinitions(self):
1192 """Returns the ClassPath constants."""
1193 ret = []
1194 template = Template("""\
1195 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
1196 native_classes = self.GetUniqueClasses(self.natives)
1197 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
1198 all_classes = native_classes
1199 all_classes.update(called_by_native_classes)
1200 for clazz in all_classes:
1201 values = {
1202 'JAVA_CLASS': clazz,
1203 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]),
1205 ret += [template.substitute(values)]
1206 ret += ''
1207 for clazz in called_by_native_classes:
1208 template = Template("""\
1209 // Leaking this jclass as we cannot use LazyInstance from some threads.
1210 jclass g_${JAVA_CLASS}_clazz = NULL;""")
1211 values = {
1212 'JAVA_CLASS': clazz,
1214 ret += [template.substitute(values)]
1215 return '\n'.join(ret)
1217 def GetFindClasses(self):
1218 """Returns the imlementation of FindClass for all known classes."""
1219 if self.init_native:
1220 template = Template("""\
1221 g_${JAVA_CLASS}_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));""")
1222 else:
1223 template = Template("""\
1224 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
1225 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
1226 ret = []
1227 for clazz in self.GetUniqueClasses(self.called_by_natives):
1228 values = {'JAVA_CLASS': clazz}
1229 ret += [template.substitute(values)]
1230 return '\n'.join(ret)
1232 def GetMethodIDImpl(self, called_by_native):
1233 """Returns the implementation of GetMethodID."""
1234 if self.options.eager_called_by_natives:
1235 template = Template("""\
1236 env->Get${STATIC_METHOD_PART}MethodID(
1237 g_${JAVA_CLASS}_clazz,
1238 "${JNI_NAME}", ${JNI_SIGNATURE});""")
1239 else:
1240 template = Template("""\
1241 base::android::MethodID::LazyGet<
1242 base::android::MethodID::TYPE_${STATIC}>(
1243 env, g_${JAVA_CLASS}_clazz,
1244 "${JNI_NAME}",
1245 ${JNI_SIGNATURE},
1246 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
1247 """)
1248 jni_name = called_by_native.name
1249 jni_return_type = called_by_native.return_type
1250 if called_by_native.is_constructor:
1251 jni_name = '<init>'
1252 jni_return_type = 'void'
1253 if called_by_native.signature:
1254 signature = called_by_native.signature
1255 else:
1256 signature = JniParams.Signature(called_by_native.params,
1257 jni_return_type,
1258 True)
1259 values = {
1260 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
1261 'JNI_NAME': jni_name,
1262 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1263 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
1264 'STATIC_METHOD_PART': 'Static' if called_by_native.static else '',
1265 'JNI_SIGNATURE': signature,
1267 return template.substitute(values)
1270 def WrapOutput(output):
1271 ret = []
1272 for line in output.splitlines():
1273 # Do not wrap lines under 80 characters or preprocessor directives.
1274 if len(line) < 80 or line.lstrip()[:1] == '#':
1275 stripped = line.rstrip()
1276 if len(ret) == 0 or len(ret[-1]) or len(stripped):
1277 ret.append(stripped)
1278 else:
1279 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
1280 subsequent_indent = first_line_indent + ' ' * 4
1281 if line.startswith('//'):
1282 subsequent_indent = '//' + subsequent_indent
1283 wrapper = textwrap.TextWrapper(width=80,
1284 subsequent_indent=subsequent_indent,
1285 break_long_words=False)
1286 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
1287 ret += ['']
1288 return '\n'.join(ret)
1291 def ExtractJarInputFile(jar_file, input_file, out_dir):
1292 """Extracts input file from jar and returns the filename.
1294 The input file is extracted to the same directory that the generated jni
1295 headers will be placed in. This is passed as an argument to script.
1297 Args:
1298 jar_file: the jar file containing the input files to extract.
1299 input_files: the list of files to extract from the jar file.
1300 out_dir: the name of the directories to extract to.
1302 Returns:
1303 the name of extracted input file.
1305 jar_file = zipfile.ZipFile(jar_file)
1307 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
1308 try:
1309 os.makedirs(out_dir)
1310 except OSError as e:
1311 if e.errno != errno.EEXIST:
1312 raise
1313 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
1314 with open(extracted_file_name, 'w') as outfile:
1315 outfile.write(jar_file.read(input_file))
1317 return extracted_file_name
1320 def GenerateJNIHeader(input_file, output_file, options):
1321 try:
1322 if os.path.splitext(input_file)[1] == '.class':
1323 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
1324 content = jni_from_javap.GetContent()
1325 else:
1326 jni_from_java_source = JNIFromJavaSource.CreateFromFile(
1327 input_file, options)
1328 content = jni_from_java_source.GetContent()
1329 except ParseError, e:
1330 print e
1331 sys.exit(1)
1332 if output_file:
1333 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
1334 os.makedirs(os.path.dirname(os.path.abspath(output_file)))
1335 if options.optimize_generation and os.path.exists(output_file):
1336 with file(output_file, 'r') as f:
1337 existing_content = f.read()
1338 if existing_content == content:
1339 return
1340 with file(output_file, 'w') as f:
1341 f.write(content)
1342 else:
1343 print output
1346 def GetScriptName():
1347 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
1348 base_index = 0
1349 for idx, value in enumerate(script_components):
1350 if value == 'base' or value == 'third_party':
1351 base_index = idx
1352 break
1353 return os.sep.join(script_components[base_index:])
1356 def main(argv):
1357 usage = """usage: %prog [OPTIONS]
1358 This script will parse the given java source code extracting the native
1359 declarations and print the header file to stdout (or a file).
1360 See SampleForTests.java for more details.
1362 option_parser = optparse.OptionParser(usage=usage)
1363 build_utils.AddDepfileOption(option_parser)
1365 option_parser.add_option('-j', '--jar_file', dest='jar_file',
1366 help='Extract the list of input files from'
1367 ' a specified jar file.'
1368 ' Uses javap to extract the methods from a'
1369 ' pre-compiled class. --input should point'
1370 ' to pre-compiled Java .class files.')
1371 option_parser.add_option('-n', dest='namespace',
1372 help='Uses as a namespace in the generated header '
1373 'instead of the javap class name, or when there is '
1374 'no JNINamespace annotation in the java source.')
1375 option_parser.add_option('--input_file',
1376 help='Single input file name. The output file name '
1377 'will be derived from it. Must be used with '
1378 '--output_dir.')
1379 option_parser.add_option('--output_dir',
1380 help='The output directory. Must be used with '
1381 '--input')
1382 option_parser.add_option('--optimize_generation', type="int",
1383 default=0, help='Whether we should optimize JNI '
1384 'generation by not regenerating files if they have '
1385 'not changed.')
1386 option_parser.add_option('--jarjar',
1387 help='Path to optional jarjar rules file.')
1388 option_parser.add_option('--script_name', default=GetScriptName(),
1389 help='The name of this script in the generated '
1390 'header.')
1391 option_parser.add_option('--includes',
1392 help='The comma-separated list of header files to '
1393 'include in the generated header.')
1394 option_parser.add_option('--pure_native_methods',
1395 action='store_true', dest='pure_native_methods',
1396 help='When true, the native methods will be called '
1397 'without any JNI-specific arguments.')
1398 option_parser.add_option('--ptr_type', default='int',
1399 type='choice', choices=['int', 'long'],
1400 help='The type used to represent native pointers in '
1401 'Java code. For 32-bit, use int; '
1402 'for 64-bit, use long.')
1403 option_parser.add_option('--jni_init_native_name', default='',
1404 help='The name of the JNI registration method that '
1405 'is used to initialize all native methods. If a '
1406 'method with this name is not present in the Java '
1407 'source file, setting this option is a no-op. When '
1408 'a method with this name is found however, the '
1409 'naming convention Java_<packageName>_<className> '
1410 'will limit the initialization to only the '
1411 'top-level class.')
1412 option_parser.add_option('--eager_called_by_natives',
1413 action='store_true', dest='eager_called_by_natives',
1414 help='When true, the called-by-native methods will '
1415 'be initialized in a non-atomic way.')
1416 option_parser.add_option('--cpp', default='cpp',
1417 help='The path to cpp command.')
1418 option_parser.add_option('--javap', default='javap',
1419 help='The path to javap command.')
1420 options, args = option_parser.parse_args(argv)
1421 if options.jar_file:
1422 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1423 options.output_dir)
1424 elif options.input_file:
1425 input_file = options.input_file
1426 else:
1427 option_parser.print_help()
1428 print '\nError: Must specify --jar_file or --input_file.'
1429 return 1
1430 output_file = None
1431 if options.output_dir:
1432 root_name = os.path.splitext(os.path.basename(input_file))[0]
1433 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
1434 if options.jarjar:
1435 with open(options.jarjar) as f:
1436 JniParams.SetJarJarMappings(f.read())
1437 GenerateJNIHeader(input_file, output_file, options)
1439 if options.depfile:
1440 build_utils.WriteDepfile(
1441 options.depfile,
1442 build_utils.GetPythonDependencies())
1445 if __name__ == '__main__':
1446 sys.exit(main(sys.argv))