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