blob: 54fea6babfb2299c8d7714ef65edbdea348ea1f0 [file] [log] [blame]
maruel@chromium.orgbe8446a2012-04-21 23:30:11 +09001#!/usr/bin/env python
bulach@chromium.org6079a072012-02-24 09:09:38 +09002# 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.
7If you change this, please run and update the tests."""
8
9import collections
torne@chromium.org108c37d2013-03-08 22:52:50 +090010import errno
bulach@chromium.org6079a072012-02-24 09:09:38 +090011import optparse
12import os
13import re
14import string
15from string import Template
16import subprocess
17import sys
18import textwrap
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +090019import zipfile
bulach@chromium.org6079a072012-02-24 09:09:38 +090020
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +090021CHROMIUM_SRC = os.path.join(
22 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
23BUILD_ANDROID_GYP = os.path.join(
24 CHROMIUM_SRC, 'build', 'android', 'gyp')
25
26sys.path.append(BUILD_ANDROID_GYP)
27
28from util import build_utils
29
bulach@chromium.org6079a072012-02-24 09:09:38 +090030
31class ParseError(Exception):
32 """Exception thrown when we can't parse the input file."""
33
34 def __init__(self, description, *context_lines):
35 Exception.__init__(self)
36 self.description = description
37 self.context_lines = context_lines
38
39 def __str__(self):
40 context = '\n'.join(self.context_lines)
41 return '***\nERROR: %s\n\n%s\n***' % (self.description, context)
42
43
44class Param(object):
45 """Describes a param for a method, either java or native."""
46
47 def __init__(self, **kwargs):
48 self.datatype = kwargs['datatype']
49 self.name = kwargs['name']
bulach@chromium.org6079a072012-02-24 09:09:38 +090050
51
52class NativeMethod(object):
53 """Describes a C/C++ method that is called by Java code"""
54
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
bulach@chromium.orga022cf62013-11-05 09:54:22 +090065 self.params[0].datatype == kwargs.get('ptr_type', 'int') and
bulach@chromium.org6079a072012-02-24 09:09:38 +090066 self.params[0].name.startswith('native')):
67 self.type = 'method'
bulach@chromium.org1c775752012-06-22 19:03:16 +090068 self.p0_type = self.params[0].name[len('native'):]
69 if kwargs.get('native_class_name'):
70 self.p0_type = kwargs['native_class_name']
bulach@chromium.org6079a072012-02-24 09:09:38 +090071 else:
72 self.type = 'function'
73 self.method_id_var_name = kwargs.get('method_id_var_name', None)
74
75
76class CalledByNative(object):
77 """Describes a java method exported to c/c++"""
78
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']
bulach@chromium.org6079a072012-02-24 09:09:38 +090085 self.name = kwargs['name']
86 self.params = kwargs['params']
87 self.method_id_var_name = kwargs.get('method_id_var_name', None)
bulach@chromium.org37fc9112013-10-26 01:27:03 +090088 self.signature = kwargs.get('signature')
bulach@chromium.org31af7532012-09-24 20:01:41 +090089 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)
bulach@chromium.org6079a072012-02-24 09:09:38 +090093
94
bulach@chromium.org658dbc02014-02-27 04:59:00 +090095class ConstantField(object):
96 def __init__(self, **kwargs):
97 self.name = kwargs['name']
98 self.value = kwargs['value']
99
100
bulach@chromium.org6079a072012-02-24 09:09:38 +0900101def JavaDataTypeToC(java_type):
102 """Returns a C datatype for the given java type."""
103 java_pod_type_map = {
104 'int': 'jint',
105 'byte': 'jbyte',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900106 'char': 'jchar',
107 'short': 'jshort',
bulach@chromium.org6079a072012-02-24 09:09:38 +0900108 'boolean': 'jboolean',
109 'long': 'jlong',
110 'double': 'jdouble',
111 'float': 'jfloat',
112 }
113 java_type_map = {
114 'void': 'void',
115 'String': 'jstring',
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900116 'java/lang/String': 'jstring',
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900117 'java/lang/Class': 'jclass',
bulach@chromium.org6079a072012-02-24 09:09:38 +0900118 }
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900119
bulach@chromium.org6079a072012-02-24 09:09:38 +0900120 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'
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900128 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'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900132 else:
133 return 'jobject'
134
135
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900136def 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)
142
143
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900144def 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': ''
156 }
157 return java_pod_type_map.get(java_type, 'NULL')
158
159
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900160class JniParams(object):
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900161 _imports = []
162 _fully_qualified_class = ''
163 _package = ''
164 _inner_classes = []
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900165 _remappings = []
bulach@chromium.org69931302014-05-08 04:16:23 +0900166 _implicit_imports = []
bulach@chromium.org6079a072012-02-24 09:09:38 +0900167
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900168 @staticmethod
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900169 def SetFullyQualifiedClass(fully_qualified_class):
170 JniParams._fully_qualified_class = 'L' + fully_qualified_class
171 JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1])
172
173 @staticmethod
qsr@chromium.org74f541e2014-05-23 23:44:37 +0900174 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]
185
186 @staticmethod
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900187 def ExtractImportsAndInnerClasses(contents):
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900188 if not JniParams._package:
189 raise RuntimeError('SetFullyQualifiedClass must be called before '
190 'ExtractImportsAndInnerClasses')
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900191 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('.', '/')]
195
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]
bulach@chromium.org6079a072012-02-24 09:09:38 +0900202
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900203 re_additional_imports = re.compile(
qsr@chromium.org74f541e2014-05-23 23:44:37 +0900204 r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900205 for match in re.finditer(re_additional_imports, contents):
qsr@chromium.org74f541e2014-05-23 23:44:37 +0900206 for class_name in match.group('class_names').split(','):
207 JniParams.AddAdditionalImport(class_name.strip())
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900208
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900209 @staticmethod
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900210 def ParseJavaPSignature(signature_line):
211 prefix = 'Signature: '
ohrn76d21492015-02-10 02:24:05 +0900212 index = signature_line.find(prefix)
213 if index == -1:
214 prefix = 'descriptor: '
215 index = signature_line.index(prefix)
216 return '"%s"' % signature_line[index + len(prefix):]
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900217
218 @staticmethod
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900219 def JavaToJni(param):
220 """Converts a java param into a JNI signature type."""
221 pod_param_map = {
222 'int': 'I',
223 'boolean': 'Z',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900224 'char': 'C',
225 'short': 'S',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900226 'long': 'J',
227 'double': 'D',
228 'float': 'F',
229 'byte': 'B',
230 'void': 'V',
231 }
232 object_param_list = [
233 'Ljava/lang/Boolean',
234 'Ljava/lang/Integer',
235 'Ljava/lang/Long',
236 'Ljava/lang/Object',
237 'Ljava/lang/String',
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900238 'Ljava/lang/Class',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900239 ]
bulach@chromium.org69931302014-05-08 04:16:23 +0900240
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900241 prefix = ''
242 # Array?
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900243 while param[-2:] == '[]':
244 prefix += '['
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900245 param = param[:-2]
246 # Generic?
247 if '<' in param:
248 param = param[:param.index('<')]
249 if param in pod_param_map:
250 return prefix + pod_param_map[param]
251 if '/' in param:
252 # Coming from javap, use the fully qualified param directly.
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900253 return prefix + 'L' + JniParams.RemapClassName(param) + ';'
bulach@chromium.org4a130cc2014-03-28 20:15:06 +0900254
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900255 for qualified_name in (object_param_list +
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900256 [JniParams._fully_qualified_class] +
257 JniParams._inner_classes):
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900258 if (qualified_name.endswith('/' + param) or
259 qualified_name.endswith('$' + param.replace('.', '$')) or
260 qualified_name == 'L' + param):
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900261 return prefix + JniParams.RemapClassName(qualified_name) + ';'
bulach@chromium.org7156c762012-11-13 23:35:00 +0900262
263 # Is it from an import? (e.g. referecing Class from import pkg.Class;
264 # note that referencing an inner class Inner from import pkg.Class.Inner
265 # is not supported).
266 for qualified_name in JniParams._imports:
267 if qualified_name.endswith('/' + param):
268 # Ensure it's not an inner class.
269 components = qualified_name.split('/')
270 if len(components) > 2 and components[-2][0].isupper():
271 raise SyntaxError('Inner class (%s) can not be imported '
272 'and used by JNI (%s). Please import the outer '
273 'class and use Outer.Inner instead.' %
274 (qualified_name, param))
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900275 return prefix + JniParams.RemapClassName(qualified_name) + ';'
bulach@chromium.org7156c762012-11-13 23:35:00 +0900276
277 # Is it an inner class from an outer class import? (e.g. referencing
278 # Class.Inner from import pkg.Class).
279 if '.' in param:
280 components = param.split('.')
281 outer = '/'.join(components[:-1])
282 inner = components[-1]
283 for qualified_name in JniParams._imports:
284 if qualified_name.endswith('/' + outer):
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900285 return (prefix + JniParams.RemapClassName(qualified_name) +
286 '$' + inner + ';')
bulach@chromium.org4a130cc2014-03-28 20:15:06 +0900287 raise SyntaxError('Inner class (%s) can not be '
288 'used directly by JNI. Please import the outer '
289 'class, probably:\n'
290 'import %s.%s;' %
291 (param, JniParams._package.replace('/', '.'),
292 outer.replace('/', '.')))
bulach@chromium.org7156c762012-11-13 23:35:00 +0900293
bulach@chromium.org69931302014-05-08 04:16:23 +0900294 JniParams._CheckImplicitImports(param)
295
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900296 # Type not found, falling back to same package as this class.
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900297 return (prefix + 'L' +
298 JniParams.RemapClassName(JniParams._package + '/' + param) + ';')
bulach@chromium.org6079a072012-02-24 09:09:38 +0900299
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900300 @staticmethod
bulach@chromium.org69931302014-05-08 04:16:23 +0900301 def _CheckImplicitImports(param):
302 # Ensure implicit imports, such as java.lang.*, are not being treated
303 # as being in the same package.
304 if not JniParams._implicit_imports:
305 # This file was generated from android.jar and lists
306 # all classes that are implicitly imported.
307 with file(os.path.join(os.path.dirname(sys.argv[0]),
308 'android_jar.classes'), 'r') as f:
309 JniParams._implicit_imports = f.readlines()
310 for implicit_import in JniParams._implicit_imports:
311 implicit_import = implicit_import.strip().replace('.class', '')
312 implicit_import = implicit_import.replace('/', '.')
313 if implicit_import.endswith('.' + param):
314 raise SyntaxError('Ambiguous class (%s) can not be used directly '
315 'by JNI.\nPlease import it, probably:\n\n'
316 'import %s;' %
317 (param, implicit_import))
318
319
320 @staticmethod
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900321 def Signature(params, returns, wrap):
322 """Returns the JNI signature for the given datatypes."""
323 items = ['(']
324 items += [JniParams.JavaToJni(param.datatype) for param in params]
325 items += [')']
326 items += [JniParams.JavaToJni(returns)]
327 if wrap:
328 return '\n' + '\n'.join(['"' + item + '"' for item in items])
329 else:
330 return '"' + ''.join(items) + '"'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900331
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900332 @staticmethod
333 def Parse(params):
334 """Parses the params into a list of Param objects."""
335 if not params:
336 return []
337 ret = []
338 for p in [p.strip() for p in params.split(',')]:
339 items = p.split(' ')
340 if 'final' in items:
341 items.remove('final')
342 param = Param(
343 datatype=items[0],
344 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
345 )
346 ret += [param]
347 return ret
bulach@chromium.org6079a072012-02-24 09:09:38 +0900348
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900349 @staticmethod
350 def RemapClassName(class_name):
351 """Remaps class names using the jarjar mapping table."""
352 for old, new in JniParams._remappings:
mkosiba@chromium.org553584f2014-06-21 07:47:21 +0900353 if old.endswith('**') and old[:-2] in class_name:
354 return class_name.replace(old[:-2], new, 1)
355 if '*' not in old and class_name.endswith(old):
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900356 return class_name.replace(old, new, 1)
mkosiba@chromium.org553584f2014-06-21 07:47:21 +0900357
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900358 return class_name
359
360 @staticmethod
361 def SetJarJarMappings(mappings):
362 """Parse jarjar mappings from a string."""
363 JniParams._remappings = []
364 for line in mappings.splitlines():
mkosiba@chromium.org553584f2014-06-21 07:47:21 +0900365 rule = line.split()
366 if rule[0] != 'rule':
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900367 continue
mkosiba@chromium.org553584f2014-06-21 07:47:21 +0900368 _, src, dest = rule
369 src = src.replace('.', '/')
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900370 dest = dest.replace('.', '/')
mkosiba@chromium.org553584f2014-06-21 07:47:21 +0900371 if src.endswith('**'):
372 src_real_name = src[:-2]
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900373 else:
mkosiba@chromium.org553584f2014-06-21 07:47:21 +0900374 assert not '*' in src
375 src_real_name = src
376
377 if dest.endswith('@0'):
378 JniParams._remappings.append((src, dest[:-2] + src_real_name))
379 elif dest.endswith('@1'):
380 assert '**' in src
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900381 JniParams._remappings.append((src, dest[:-2]))
mkosiba@chromium.org553584f2014-06-21 07:47:21 +0900382 else:
383 assert not '@' in dest
384 JniParams._remappings.append((src, dest))
torne@chromium.org6cbf5b92013-05-29 22:51:23 +0900385
bulach@chromium.org6079a072012-02-24 09:09:38 +0900386
bulach@chromium.org1c775752012-06-22 19:03:16 +0900387def ExtractJNINamespace(contents):
388 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
389 m = re.findall(re_jni_namespace, contents)
390 if not m:
391 return ''
392 return m[0]
393
394
bulach@chromium.org6079a072012-02-24 09:09:38 +0900395def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
396 re_package = re.compile('.*?package (.*?);')
397 matches = re.findall(re_package, contents)
398 if not matches:
399 raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
400 return (matches[0].replace('.', '/') + '/' +
401 os.path.splitext(os.path.basename(java_file_name))[0])
402
403
bulach@chromium.orga022cf62013-11-05 09:54:22 +0900404def ExtractNatives(contents, ptr_type):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900405 """Returns a list of dict containing information about a native method."""
406 contents = contents.replace('\n', '')
407 natives = []
bulach@chromium.org1c775752012-06-22 19:03:16 +0900408 re_native = re.compile(r'(@NativeClassQualifiedName'
bulach@chromium.org86eae282014-05-10 21:15:07 +0900409 '\(\"(?P<native_class_name>.*?)\"\)\s+)?'
410 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
411 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
412 '(?P<return_type>\S*) '
413 '(?P<name>native\w+)\((?P<params>.*?)\);')
bulach@chromium.org1c775752012-06-22 19:03:16 +0900414 for match in re.finditer(re_native, contents):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900415 native = NativeMethod(
bulach@chromium.org1c775752012-06-22 19:03:16 +0900416 static='static' in match.group('qualifiers'),
417 java_class_name=match.group('java_class_name'),
418 native_class_name=match.group('native_class_name'),
bulach@chromium.orga6e185e2013-03-26 16:32:39 +0900419 return_type=match.group('return_type'),
bulach@chromium.org1c775752012-06-22 19:03:16 +0900420 name=match.group('name').replace('native', ''),
bulach@chromium.orga022cf62013-11-05 09:54:22 +0900421 params=JniParams.Parse(match.group('params')),
422 ptr_type=ptr_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900423 natives += [native]
424 return natives
425
426
bulach@chromium.org31af7532012-09-24 20:01:41 +0900427def GetStaticCastForReturnType(return_type):
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900428 type_map = { 'String' : 'jstring',
429 'java/lang/String' : 'jstring',
430 'boolean[]': 'jbooleanArray',
431 'byte[]': 'jbyteArray',
432 'char[]': 'jcharArray',
433 'short[]': 'jshortArray',
434 'int[]': 'jintArray',
435 'long[]': 'jlongArray',
skhatri@nvidia.comf5bf8c42014-06-02 21:23:12 +0900436 'float[]': 'jfloatArray',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900437 'double[]': 'jdoubleArray' }
438 ret = type_map.get(return_type, None)
439 if ret:
440 return ret
441 if return_type.endswith('[]'):
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900442 return 'jobjectArray'
bulach@chromium.org31af7532012-09-24 20:01:41 +0900443 return None
444
445
446def GetEnvCall(is_constructor, is_static, return_type):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900447 """Maps the types availabe via env->Call__Method."""
bulach@chromium.org31af7532012-09-24 20:01:41 +0900448 if is_constructor:
449 return 'NewObject'
450 env_call_map = {'boolean': 'Boolean',
451 'byte': 'Byte',
452 'char': 'Char',
453 'short': 'Short',
454 'int': 'Int',
455 'long': 'Long',
456 'float': 'Float',
457 'void': 'Void',
458 'double': 'Double',
459 'Object': 'Object',
bulach@chromium.org6079a072012-02-24 09:09:38 +0900460 }
bulach@chromium.org31af7532012-09-24 20:01:41 +0900461 call = env_call_map.get(return_type, 'Object')
462 if is_static:
463 call = 'Static' + call
464 return 'Call' + call + 'Method'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900465
466
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900467def GetMangledParam(datatype):
468 """Returns a mangled identifier for the datatype."""
469 if len(datatype) <= 2:
470 return datatype.replace('[', 'A')
471 ret = ''
472 for i in range(1, len(datatype)):
473 c = datatype[i]
474 if c == '[':
475 ret += 'A'
476 elif c.isupper() or datatype[i - 1] in ['/', 'L']:
477 ret += c.upper()
478 return ret
479
480
481def GetMangledMethodName(name, params, return_type):
482 """Returns a mangled method name for the given signature.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900483
484 The returned name can be used as a C identifier and will be unique for all
485 valid overloads of the same method.
486
487 Args:
488 name: string.
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900489 params: list of Param.
490 return_type: string.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900491
492 Returns:
493 A mangled name.
494 """
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900495 mangled_items = []
496 for datatype in [return_type] + [x.datatype for x in params]:
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900497 mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))]
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900498 mangled_name = name + '_'.join(mangled_items)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900499 assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
500 return mangled_name
501
502
503def MangleCalledByNatives(called_by_natives):
504 """Mangles all the overloads from the call_by_natives list."""
505 method_counts = collections.defaultdict(
506 lambda: collections.defaultdict(lambda: 0))
507 for called_by_native in called_by_natives:
508 java_class_name = called_by_native.java_class_name
509 name = called_by_native.name
510 method_counts[java_class_name][name] += 1
511 for called_by_native in called_by_natives:
512 java_class_name = called_by_native.java_class_name
513 method_name = called_by_native.name
514 method_id_var_name = method_name
515 if method_counts[java_class_name][method_name] > 1:
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900516 method_id_var_name = GetMangledMethodName(method_name,
517 called_by_native.params,
518 called_by_native.return_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900519 called_by_native.method_id_var_name = method_id_var_name
520 return called_by_natives
521
522
523# Regex to match the JNI return types that should be included in a
524# ScopedJavaLocalRef.
525RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array')
526
527# Regex to match a string like "@CalledByNative public void foo(int bar)".
528RE_CALLED_BY_NATIVE = re.compile(
529 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
530 '\s+(?P<prefix>[\w ]*?)'
bulach@chromium.orga6e185e2013-03-26 16:32:39 +0900531 '\s*(?P<return_type>\S+?)'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900532 '\s+(?P<name>\w+)'
533 '\s*\((?P<params>[^\)]*)\)')
534
535
536def ExtractCalledByNatives(contents):
537 """Parses all methods annotated with @CalledByNative.
538
539 Args:
540 contents: the contents of the java file.
541
542 Returns:
543 A list of dict with information about the annotated methods.
544 TODO(bulach): return a CalledByNative object.
545
546 Raises:
547 ParseError: if unable to parse.
548 """
549 called_by_natives = []
550 for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
551 called_by_natives += [CalledByNative(
552 system_class=False,
553 unchecked='Unchecked' in match.group('Unchecked'),
554 static='static' in match.group('prefix'),
555 java_class_name=match.group('annotation') or '',
556 return_type=match.group('return_type'),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900557 name=match.group('name'),
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900558 params=JniParams.Parse(match.group('params')))]
bulach@chromium.org6079a072012-02-24 09:09:38 +0900559 # Check for any @CalledByNative occurrences that weren't matched.
560 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
561 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
562 if '@CalledByNative' in line1:
563 raise ParseError('could not parse @CalledByNative method signature',
564 line1, line2)
565 return MangleCalledByNatives(called_by_natives)
566
567
568class JNIFromJavaP(object):
569 """Uses 'javap' to parse a .class file and generate the JNI header file."""
570
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900571 def __init__(self, contents, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900572 self.contents = contents
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900573 self.namespace = options.namespace
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900574 for line in contents:
575 class_name = re.match(
576 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
577 line)
578 if class_name:
579 self.fully_qualified_class = class_name.group('class_name')
580 break
bulach@chromium.org6079a072012-02-24 09:09:38 +0900581 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
simonb@opera.com521714e2013-09-04 06:22:48 +0900582 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
583 # away the <...> and use the raw class name that Java 6 would've given us.
584 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900585 JniParams.SetFullyQualifiedClass(self.fully_qualified_class)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900586 self.java_class_name = self.fully_qualified_class.split('/')[-1]
587 if not self.namespace:
588 self.namespace = 'JNI_' + self.java_class_name
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900589 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
bulach@chromium.org31af7532012-09-24 20:01:41 +0900590 '\((?P<params>.*?)\)')
bulach@chromium.org6079a072012-02-24 09:09:38 +0900591 self.called_by_natives = []
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900592 for lineno, content in enumerate(contents[2:], 2):
bulach@chromium.org31af7532012-09-24 20:01:41 +0900593 match = re.match(re_method, content)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900594 if not match:
595 continue
596 self.called_by_natives += [CalledByNative(
597 system_class=True,
598 unchecked=False,
bulach@chromium.org31af7532012-09-24 20:01:41 +0900599 static='static' in match.group('prefix'),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900600 java_class_name='',
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900601 return_type=match.group('return_type').replace('.', '/'),
bulach@chromium.org31af7532012-09-24 20:01:41 +0900602 name=match.group('name'),
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900603 params=JniParams.Parse(match.group('params').replace('.', '/')),
604 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
605 re_constructor = re.compile('(.*?)public ' +
bulach@chromium.org31af7532012-09-24 20:01:41 +0900606 self.fully_qualified_class.replace('/', '.') +
607 '\((?P<params>.*?)\)')
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900608 for lineno, content in enumerate(contents[2:], 2):
bulach@chromium.org31af7532012-09-24 20:01:41 +0900609 match = re.match(re_constructor, content)
610 if not match:
611 continue
612 self.called_by_natives += [CalledByNative(
bulach@chromium.org208f05b2012-10-03 06:13:55 +0900613 system_class=True,
bulach@chromium.org31af7532012-09-24 20:01:41 +0900614 unchecked=False,
615 static=False,
616 java_class_name='',
617 return_type=self.fully_qualified_class,
618 name='Constructor',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900619 params=JniParams.Parse(match.group('params').replace('.', '/')),
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900620 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
bulach@chromium.org31af7532012-09-24 20:01:41 +0900621 is_constructor=True)]
bulach@chromium.org6079a072012-02-24 09:09:38 +0900622 self.called_by_natives = MangleCalledByNatives(self.called_by_natives)
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900623
624 self.constant_fields = []
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900625 re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900626 re_constant_field_value = re.compile(
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900627 '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)')
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900628 for lineno, content in enumerate(contents[2:], 2):
629 match = re.match(re_constant_field, content)
630 if not match:
631 continue
632 value = re.match(re_constant_field_value, contents[lineno + 2])
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900633 if not value:
634 value = re.match(re_constant_field_value, contents[lineno + 3])
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900635 if value:
636 self.constant_fields.append(
637 ConstantField(name=match.group('name'),
638 value=value.group('value')))
639
bulach@chromium.org6079a072012-02-24 09:09:38 +0900640 self.inl_header_file_generator = InlHeaderFileGenerator(
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900641 self.namespace, self.fully_qualified_class, [],
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900642 self.called_by_natives, self.constant_fields, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900643
644 def GetContent(self):
645 return self.inl_header_file_generator.GetContent()
646
647 @staticmethod
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900648 def CreateFromClass(class_file, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900649 class_name = os.path.splitext(os.path.basename(class_file))[0]
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900650 p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
651 '-s', class_name],
bulach@chromium.org6079a072012-02-24 09:09:38 +0900652 cwd=os.path.dirname(class_file),
653 stdout=subprocess.PIPE,
654 stderr=subprocess.PIPE)
655 stdout, _ = p.communicate()
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900656 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900657 return jni_from_javap
658
659
660class JNIFromJavaSource(object):
661 """Uses the given java source file to generate the JNI header file."""
662
torne@chromium.org202d2672014-04-15 02:29:59 +0900663 # Match single line comments, multiline comments, character literals, and
664 # double-quoted strings.
665 _comment_remover_regex = re.compile(
666 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
667 re.DOTALL | re.MULTILINE)
668
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900669 def __init__(self, contents, fully_qualified_class, options):
torne@chromium.org202d2672014-04-15 02:29:59 +0900670 contents = self._RemoveComments(contents)
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900671 JniParams.SetFullyQualifiedClass(fully_qualified_class)
672 JniParams.ExtractImportsAndInnerClasses(contents)
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900673 jni_namespace = ExtractJNINamespace(contents) or options.namespace
bulach@chromium.orga022cf62013-11-05 09:54:22 +0900674 natives = ExtractNatives(contents, options.ptr_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900675 called_by_natives = ExtractCalledByNatives(contents)
bulach@chromium.orge2530d02012-07-03 01:23:35 +0900676 if len(natives) == 0 and len(called_by_natives) == 0:
677 raise SyntaxError('Unable to find any JNI methods for %s.' %
678 fully_qualified_class)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900679 inl_header_file_generator = InlHeaderFileGenerator(
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900680 jni_namespace, fully_qualified_class, natives, called_by_natives,
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900681 [], options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900682 self.content = inl_header_file_generator.GetContent()
683
torne@chromium.org202d2672014-04-15 02:29:59 +0900684 @classmethod
685 def _RemoveComments(cls, contents):
husky@chromium.org8c438d72012-07-31 02:27:48 +0900686 # We need to support both inline and block comments, and we need to handle
torne@chromium.org202d2672014-04-15 02:29:59 +0900687 # strings that contain '//' or '/*'.
688 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
husky@chromium.org8c438d72012-07-31 02:27:48 +0900689 # parser. Maybe we could ditch JNIFromJavaSource and just always use
690 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
691 # http://code.google.com/p/chromium/issues/detail?id=138941
torne@chromium.org202d2672014-04-15 02:29:59 +0900692 def replacer(match):
693 # Replace matches that are comments with nothing; return literals/strings
694 # unchanged.
695 s = match.group(0)
696 if s.startswith('/'):
697 return ''
698 else:
699 return s
700 return cls._comment_remover_regex.sub(replacer, contents)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900701
702 def GetContent(self):
703 return self.content
704
705 @staticmethod
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900706 def CreateFromFile(java_file_name, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900707 contents = file(java_file_name).read()
708 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
709 contents)
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900710 return JNIFromJavaSource(contents, fully_qualified_class, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900711
712
713class InlHeaderFileGenerator(object):
714 """Generates an inline header file for JNI integration."""
715
716 def __init__(self, namespace, fully_qualified_class, natives,
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900717 called_by_natives, constant_fields, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900718 self.namespace = namespace
719 self.fully_qualified_class = fully_qualified_class
720 self.class_name = self.fully_qualified_class.split('/')[-1]
721 self.natives = natives
722 self.called_by_natives = called_by_natives
723 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900724 self.constant_fields = constant_fields
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900725 self.options = options
726 self.init_native = self.ExtractInitNative(options)
727
728 def ExtractInitNative(self, options):
729 for native in self.natives:
bulach@chromium.org92d0dac2014-01-17 01:08:05 +0900730 if options.jni_init_native_name == 'native' + native.name:
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900731 self.natives.remove(native)
732 return native
733 return None
bulach@chromium.org6079a072012-02-24 09:09:38 +0900734
735 def GetContent(self):
736 """Returns the content of the JNI binding file."""
737 template = Template("""\
mkosiba@chromium.org33075172014-08-22 01:37:32 +0900738// Copyright 2014 The Chromium Authors. All rights reserved.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900739// Use of this source code is governed by a BSD-style license that can be
740// found in the LICENSE file.
741
742
743// This file is autogenerated by
744// ${SCRIPT_NAME}
745// For
746// ${FULLY_QUALIFIED_CLASS}
747
748#ifndef ${HEADER_GUARD}
749#define ${HEADER_GUARD}
750
751#include <jni.h>
752
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900753${INCLUDES}
bulach@chromium.org6079a072012-02-24 09:09:38 +0900754
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900755#include "base/android/jni_int_wrapper.h"
756
bulach@chromium.org6079a072012-02-24 09:09:38 +0900757// Step 1: forward declarations.
758namespace {
759$CLASS_PATH_DEFINITIONS
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900760$METHOD_ID_DEFINITIONS
bulach@chromium.org6079a072012-02-24 09:09:38 +0900761} // namespace
bulach@chromium.org1c775752012-06-22 19:03:16 +0900762
763$OPEN_NAMESPACE
bulach@chromium.org6079a072012-02-24 09:09:38 +0900764$FORWARD_DECLARATIONS
765
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900766$CONSTANT_FIELDS
767
bulach@chromium.org6079a072012-02-24 09:09:38 +0900768// Step 2: method stubs.
769$METHOD_STUBS
770
bulach@chromium.org231cff62012-10-17 03:35:10 +0900771// Step 3: RegisterNatives.
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900772$JNI_NATIVE_METHODS
773$REGISTER_NATIVES
bulach@chromium.org6079a072012-02-24 09:09:38 +0900774$CLOSE_NAMESPACE
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900775$JNI_REGISTER_NATIVES
bulach@chromium.org6079a072012-02-24 09:09:38 +0900776#endif // ${HEADER_GUARD}
777""")
bulach@chromium.org6079a072012-02-24 09:09:38 +0900778 values = {
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900779 'SCRIPT_NAME': self.options.script_name,
bulach@chromium.org6079a072012-02-24 09:09:38 +0900780 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
781 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900782 'METHOD_ID_DEFINITIONS': self.GetMethodIDDefinitionsString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900783 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(),
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900784 'CONSTANT_FIELDS': self.GetConstantFieldsString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900785 'METHOD_STUBS': self.GetMethodStubsString(),
786 'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900787 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(),
788 'REGISTER_NATIVES': self.GetRegisterNativesString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900789 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
790 'HEADER_GUARD': self.header_guard,
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900791 'INCLUDES': self.GetIncludesString(),
792 'JNI_REGISTER_NATIVES': self.GetJNIRegisterNativesString()
bulach@chromium.org6079a072012-02-24 09:09:38 +0900793 }
794 return WrapOutput(template.substitute(values))
795
796 def GetClassPathDefinitionsString(self):
797 ret = []
798 ret += [self.GetClassPathDefinitions()]
799 return '\n'.join(ret)
800
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900801 def GetMethodIDDefinitionsString(self):
802 """Returns the definition of method ids for the called by native methods."""
803 if not self.options.eager_called_by_natives:
804 return ''
805 template = Template("""\
806jmethodID g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = NULL;""")
807 ret = []
808 for called_by_native in self.called_by_natives:
809 values = {
810 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
811 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
812 }
813 ret += [template.substitute(values)]
814 return '\n'.join(ret)
815
bulach@chromium.org6079a072012-02-24 09:09:38 +0900816 def GetForwardDeclarationsString(self):
817 ret = []
818 for native in self.natives:
819 if native.type != 'method':
820 ret += [self.GetForwardDeclaration(native)]
mkosiba@chromium.org56092f02014-08-11 20:27:12 +0900821 if self.options.native_exports and ret:
822 return '\nextern "C" {\n' + "\n".join(ret) + '\n}; // extern "C"'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900823 return '\n'.join(ret)
824
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900825 def GetConstantFieldsString(self):
826 if not self.constant_fields:
827 return ''
828 ret = ['enum Java_%s_constant_fields {' % self.class_name]
829 for c in self.constant_fields:
830 ret += [' %s = %s,' % (c.name, c.value)]
831 ret += ['};']
832 return '\n'.join(ret)
833
bulach@chromium.org6079a072012-02-24 09:09:38 +0900834 def GetMethodStubsString(self):
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900835 """Returns the code corresponding to method stubs."""
bulach@chromium.org6079a072012-02-24 09:09:38 +0900836 ret = []
837 for native in self.natives:
838 if native.type == 'method':
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900839 ret += [self.GetNativeMethodStubString(native)]
840 if self.options.eager_called_by_natives:
841 ret += self.GetEagerCalledByNativeMethodStubs()
842 else:
843 ret += self.GetLazyCalledByNativeMethodStubs()
mkosiba@chromium.org56092f02014-08-11 20:27:12 +0900844
845 if self.options.native_exports and ret:
846 return '\nextern "C" {\n' + "\n".join(ret) + '\n}; // extern "C"'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900847 return '\n'.join(ret)
848
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900849 def GetLazyCalledByNativeMethodStubs(self):
850 return [self.GetLazyCalledByNativeMethodStub(called_by_native)
851 for called_by_native in self.called_by_natives]
852
853 def GetEagerCalledByNativeMethodStubs(self):
854 ret = []
855 if self.called_by_natives:
856 ret += ['namespace {']
857 for called_by_native in self.called_by_natives:
858 ret += [self.GetEagerCalledByNativeMethodStub(called_by_native)]
859 ret += ['} // namespace']
860 return ret
861
862 def GetIncludesString(self):
863 if not self.options.includes:
864 return ''
865 includes = self.options.includes.split(',')
866 return '\n'.join('#include "%s"' % x for x in includes)
867
bulach@chromium.org6079a072012-02-24 09:09:38 +0900868 def GetKMethodsString(self, clazz):
869 ret = []
870 for native in self.natives:
871 if (native.java_class_name == clazz or
872 (not native.java_class_name and clazz == self.class_name)):
873 ret += [self.GetKMethodArrayEntry(native)]
874 return '\n'.join(ret)
875
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900876 def SubstituteNativeMethods(self, template):
877 """Substitutes JAVA_CLASS and KMETHODS in the provided template."""
878 ret = []
bulach@chromium.org6079a072012-02-24 09:09:38 +0900879 all_classes = self.GetUniqueClasses(self.natives)
880 all_classes[self.class_name] = self.fully_qualified_class
881 for clazz in all_classes:
882 kmethods = self.GetKMethodsString(clazz)
883 if kmethods:
884 values = {'JAVA_CLASS': clazz,
885 'KMETHODS': kmethods}
886 ret += [template.substitute(values)]
887 if not ret: return ''
888 return '\n' + '\n'.join(ret)
889
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900890 def GetJNINativeMethodsString(self):
891 """Returns the implementation of the array of native methods."""
torne0dd0b0d2015-02-21 08:06:33 +0900892 if self.options.native_exports and not self.options.native_exports_optional:
mkosiba@chromium.org56092f02014-08-11 20:27:12 +0900893 return ''
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900894 template = Template("""\
895static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
896${KMETHODS}
897};
898""")
899 return self.SubstituteNativeMethods(template)
900
901 def GetRegisterCalledByNativesImplString(self):
902 """Returns the code for registering the called by native methods."""
903 if not self.options.eager_called_by_natives:
904 return ''
905 template = Template("""\
906 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = ${GET_METHOD_ID_IMPL}
907 if (g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} == NULL) {
908 return false;
909 }
910 """)
911 ret = []
912 for called_by_native in self.called_by_natives:
913 values = {
914 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
915 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
916 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native),
917 }
918 ret += [template.substitute(values)]
919 return '\n'.join(ret)
920
921 def GetRegisterNativesString(self):
922 """Returns the code for RegisterNatives."""
923 template = Template("""\
924${REGISTER_NATIVES_SIGNATURE} {
torne0dd0b0d2015-02-21 08:06:33 +0900925${EARLY_EXIT}${CLASSES}
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900926${NATIVES}
927${CALLED_BY_NATIVES}
928 return true;
929}
930""")
931 signature = 'static bool RegisterNativesImpl(JNIEnv* env'
932 if self.init_native:
933 signature += ', jclass clazz)'
934 else:
935 signature += ')'
936
torne0dd0b0d2015-02-21 08:06:33 +0900937 early_exit = ''
938 if self.options.native_exports_optional:
939 early_exit = """\
940 if (base::android::IsManualJniRegistrationDisabled()) return true;
941"""
942
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900943 natives = self.GetRegisterNativesImplString()
944 called_by_natives = self.GetRegisterCalledByNativesImplString()
945 values = {'REGISTER_NATIVES_SIGNATURE': signature,
torne0dd0b0d2015-02-21 08:06:33 +0900946 'EARLY_EXIT': early_exit,
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900947 'CLASSES': self.GetFindClasses(),
948 'NATIVES': natives,
949 'CALLED_BY_NATIVES': called_by_natives,
950 }
951 return template.substitute(values)
952
953 def GetRegisterNativesImplString(self):
954 """Returns the shared implementation for RegisterNatives."""
torne0dd0b0d2015-02-21 08:06:33 +0900955 if self.options.native_exports and not self.options.native_exports_optional:
mkosiba@chromium.org56092f02014-08-11 20:27:12 +0900956 return ''
957
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900958 template = Template("""\
959 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
960
mkosiba@chromium.org33075172014-08-22 01:37:32 +0900961 if (env->RegisterNatives(${JAVA_CLASS}_clazz(env),
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900962 kMethods${JAVA_CLASS},
963 kMethods${JAVA_CLASS}Size) < 0) {
964 jni_generator::HandleRegistrationError(
mkosiba@chromium.org33075172014-08-22 01:37:32 +0900965 env, ${JAVA_CLASS}_clazz(env), __FILE__);
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900966 return false;
967 }
968""")
969 return self.SubstituteNativeMethods(template)
970
971 def GetJNIRegisterNativesString(self):
972 """Returns the implementation for the JNI registration of native methods."""
973 if not self.init_native:
974 return ''
975
976 template = Template("""\
977extern "C" JNIEXPORT bool JNICALL
978Java_${FULLY_QUALIFIED_CLASS}_${INIT_NATIVE_NAME}(JNIEnv* env, jclass clazz) {
979 return ${NAMESPACE}RegisterNativesImpl(env, clazz);
980}
981""")
mkosiba@chromium.org56092f02014-08-11 20:27:12 +0900982
983 if self.options.native_exports:
984 java_name = JniParams.RemapClassName(self.fully_qualified_class)
985 java_name = java_name.replace('_', '_1').replace('/', '_')
986 else:
987 java_name = self.fully_qualified_class.replace('/', '_')
988
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900989 namespace = ''
990 if self.namespace:
991 namespace = self.namespace + '::'
mkosiba@chromium.org56092f02014-08-11 20:27:12 +0900992 values = {'FULLY_QUALIFIED_CLASS': java_name,
bulach@chromium.org92d0dac2014-01-17 01:08:05 +0900993 'INIT_NATIVE_NAME': 'native' + self.init_native.name,
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900994 'NAMESPACE': namespace,
995 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString()
996 }
997 return template.substitute(values)
998
bulach@chromium.org6079a072012-02-24 09:09:38 +0900999 def GetOpenNamespaceString(self):
1000 if self.namespace:
bulach@chromium.org16dde202012-07-24 00:31:35 +09001001 all_namespaces = ['namespace %s {' % ns
1002 for ns in self.namespace.split('::')]
1003 return '\n'.join(all_namespaces)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001004 return ''
1005
1006 def GetCloseNamespaceString(self):
1007 if self.namespace:
bulach@chromium.org16dde202012-07-24 00:31:35 +09001008 all_namespaces = ['} // namespace %s' % ns
1009 for ns in self.namespace.split('::')]
1010 all_namespaces.reverse()
1011 return '\n'.join(all_namespaces) + '\n'
bulach@chromium.org6079a072012-02-24 09:09:38 +09001012 return ''
1013
1014 def GetJNIFirstParam(self, native):
1015 ret = []
1016 if native.type == 'method':
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001017 ret = ['jobject jcaller']
bulach@chromium.org6079a072012-02-24 09:09:38 +09001018 elif native.type == 'function':
1019 if native.static:
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001020 ret = ['jclass jcaller']
bulach@chromium.org6079a072012-02-24 09:09:38 +09001021 else:
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001022 ret = ['jobject jcaller']
bulach@chromium.org6079a072012-02-24 09:09:38 +09001023 return ret
1024
1025 def GetParamsInDeclaration(self, native):
1026 """Returns the params for the stub declaration.
1027
1028 Args:
1029 native: the native dictionary describing the method.
1030
1031 Returns:
1032 A string containing the params.
1033 """
1034 return ',\n '.join(self.GetJNIFirstParam(native) +
1035 [JavaDataTypeToC(param.datatype) + ' ' +
1036 param.name
1037 for param in native.params])
1038
1039 def GetCalledByNativeParamsInDeclaration(self, called_by_native):
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001040 return ',\n '.join([
1041 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
1042 param.name
1043 for param in called_by_native.params])
bulach@chromium.org6079a072012-02-24 09:09:38 +09001044
torne94bda722015-02-20 21:37:52 +09001045 def GetStubName(self, native):
1046 """Return the name of the stub function for this native method.
1047
1048 Args:
1049 native: the native dictionary describing the method.
1050
1051 Returns:
1052 A string with the stub function name. For native exports mode this is the
1053 Java_* symbol name required by the JVM; otherwise it is just the name of
1054 the native method itself.
1055 """
1056 if self.options.native_exports:
1057 template = Template("Java_${JAVA_NAME}_native${NAME}")
1058
1059 java_name = JniParams.RemapClassName(self.fully_qualified_class)
1060 java_name = java_name.replace('_', '_1').replace('/', '_')
1061 if native.java_class_name:
1062 java_name += '_00024' + native.java_class_name
1063
1064 values = {'NAME': native.name,
1065 'JAVA_NAME': java_name}
1066 return template.substitute(values)
1067 else:
1068 return native.name
1069
bulach@chromium.org6079a072012-02-24 09:09:38 +09001070 def GetForwardDeclaration(self, native):
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001071 template_str = """
pliard@chromium.org5fa58ac2012-07-02 19:31:31 +09001072static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001073"""
1074 if self.options.native_exports:
1075 template_str += """
torne09434e52015-01-09 04:48:31 +09001076__attribute__((visibility("default"), alias("${NAME}")))
torne94bda722015-02-20 21:37:52 +09001077${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS});
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001078"""
1079 template = Template(template_str)
1080 params_in_call = []
1081 if not self.options.pure_native_methods:
1082 params_in_call = ['env', 'jcaller']
1083 params_in_call = ', '.join(params_in_call + [p.name for p in native.params])
1084
bulach@chromium.org16dde202012-07-24 00:31:35 +09001085 values = {'RETURN': JavaDataTypeToC(native.return_type),
bulach@chromium.org6079a072012-02-24 09:09:38 +09001086 'NAME': native.name,
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001087 'PARAMS': self.GetParamsInDeclaration(native),
torne94bda722015-02-20 21:37:52 +09001088 'PARAMS_IN_CALL': params_in_call,
1089 'STUB_NAME': self.GetStubName(native)}
bulach@chromium.org6079a072012-02-24 09:09:38 +09001090 return template.substitute(values)
1091
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001092 def GetNativeMethodStubString(self, native):
bulach@chromium.org6079a072012-02-24 09:09:38 +09001093 """Returns stubs for native methods."""
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001094 if self.options.native_exports:
1095 template_str = """\
1096__attribute__((visibility("default")))
torne94bda722015-02-20 21:37:52 +09001097${RETURN} ${STUB_NAME}(JNIEnv* env,
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001098 ${PARAMS_IN_DECLARATION}) {"""
1099 else:
1100 template_str = """\
torne94bda722015-02-20 21:37:52 +09001101static ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {"""
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001102 template_str += """
bulach@chromium.org6079a072012-02-24 09:09:38 +09001103 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001104 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
1105 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
bulach@chromium.org6079a072012-02-24 09:09:38 +09001106}
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001107"""
1108
1109 template = Template(template_str)
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001110 params = []
1111 if not self.options.pure_native_methods:
1112 params = ['env', 'jcaller']
1113 params_in_call = ', '.join(params + [p.name for p in native.params[1:]])
bulach@chromium.org6079a072012-02-24 09:09:38 +09001114
1115 return_type = JavaDataTypeToC(native.return_type)
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001116 optional_error_return = JavaReturnValueToC(native.return_type)
1117 if optional_error_return:
1118 optional_error_return = ', ' + optional_error_return
1119 post_call = ''
bulach@chromium.org6079a072012-02-24 09:09:38 +09001120 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
bulach@chromium.org6079a072012-02-24 09:09:38 +09001121 post_call = '.Release()'
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001122
bulach@chromium.org6079a072012-02-24 09:09:38 +09001123 values = {
1124 'RETURN': return_type,
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001125 'OPTIONAL_ERROR_RETURN': optional_error_return,
bulach@chromium.org6079a072012-02-24 09:09:38 +09001126 'NAME': native.name,
1127 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native),
1128 'PARAM0_NAME': native.params[0].name,
1129 'P0_TYPE': native.p0_type,
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001130 'PARAMS_IN_CALL': params_in_call,
torne94bda722015-02-20 21:37:52 +09001131 'POST_CALL': post_call,
1132 'STUB_NAME': self.GetStubName(native),
bulach@chromium.org6079a072012-02-24 09:09:38 +09001133 }
1134 return template.substitute(values)
1135
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001136 def GetArgument(self, param):
1137 return ('as_jint(' + param.name + ')'
1138 if param.datatype == 'int' else param.name)
1139
1140 def GetArgumentsInCall(self, params):
1141 """Return a string of arguments to call from native into Java"""
1142 return [self.GetArgument(p) for p in params]
1143
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001144 def GetCalledByNativeValues(self, called_by_native):
1145 """Fills in necessary values for the CalledByNative methods."""
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001146 java_class = called_by_native.java_class_name or self.class_name
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001147 if called_by_native.static or called_by_native.is_constructor:
1148 first_param_in_declaration = ''
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001149 first_param_in_call = ('%s_clazz(env)' % java_class)
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001150 else:
1151 first_param_in_declaration = ', jobject obj'
1152 first_param_in_call = 'obj'
1153 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
1154 called_by_native)
1155 if params_in_declaration:
1156 params_in_declaration = ', ' + params_in_declaration
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001157 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001158 if params_in_call:
1159 params_in_call = ', ' + params_in_call
1160 pre_call = ''
1161 post_call = ''
1162 if called_by_native.static_cast:
1163 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
1164 post_call = ')'
1165 check_exception = ''
1166 if not called_by_native.unchecked:
1167 check_exception = 'jni_generator::CheckException(env);'
1168 return_type = JavaDataTypeToC(called_by_native.return_type)
1169 optional_error_return = JavaReturnValueToC(called_by_native.return_type)
1170 if optional_error_return:
1171 optional_error_return = ', ' + optional_error_return
1172 return_declaration = ''
1173 return_clause = ''
1174 if return_type != 'void':
1175 pre_call = ' ' + pre_call
1176 return_declaration = return_type + ' ret ='
1177 if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
1178 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
1179 return_clause = 'return ' + return_type + '(env, ret);'
1180 else:
1181 return_clause = 'return ret;'
1182 return {
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001183 'JAVA_CLASS': java_class,
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001184 'RETURN_TYPE': return_type,
1185 'OPTIONAL_ERROR_RETURN': optional_error_return,
1186 'RETURN_DECLARATION': return_declaration,
1187 'RETURN_CLAUSE': return_clause,
1188 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
1189 'PARAMS_IN_DECLARATION': params_in_declaration,
1190 'PRE_CALL': pre_call,
1191 'POST_CALL': post_call,
1192 'ENV_CALL': called_by_native.env_call,
1193 'FIRST_PARAM_IN_CALL': first_param_in_call,
1194 'PARAMS_IN_CALL': params_in_call,
1195 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1196 'CHECK_EXCEPTION': check_exception,
1197 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native)
1198 }
1199
1200 def GetEagerCalledByNativeMethodStub(self, called_by_native):
1201 """Returns the implementation of the called by native method."""
1202 template = Template("""
1203static ${RETURN_TYPE} ${METHOD_ID_VAR_NAME}(\
1204JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION}) {
1205 ${RETURN_DECLARATION}${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1206 g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}${PARAMS_IN_CALL})${POST_CALL};
1207 ${RETURN_CLAUSE}
1208}""")
1209 values = self.GetCalledByNativeValues(called_by_native)
1210 return template.substitute(values)
1211
1212 def GetLazyCalledByNativeMethodStub(self, called_by_native):
bulach@chromium.org6079a072012-02-24 09:09:38 +09001213 """Returns a string."""
1214 function_signature_template = Template("""\
bulach@chromium.org0c6805b2012-09-28 21:34:33 +09001215static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
bulach@chromium.org6079a072012-02-24 09:09:38 +09001216JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
1217 function_header_template = Template("""\
1218${FUNCTION_SIGNATURE} {""")
1219 function_header_with_unused_template = Template("""\
1220${FUNCTION_SIGNATURE} __attribute__ ((unused));
1221${FUNCTION_SIGNATURE} {""")
1222 template = Template("""
bulach@chromium.org231cff62012-10-17 03:35:10 +09001223static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
bulach@chromium.org6079a072012-02-24 09:09:38 +09001224${FUNCTION_HEADER}
1225 /* Must call RegisterNativesImpl() */
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001226 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001227 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN});
bulach@chromium.org231cff62012-10-17 03:35:10 +09001228 jmethodID method_id =
1229 ${GET_METHOD_ID_IMPL}
bulach@chromium.org6079a072012-02-24 09:09:38 +09001230 ${RETURN_DECLARATION}
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001231 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1232 method_id${PARAMS_IN_CALL})${POST_CALL};
bulach@chromium.org6079a072012-02-24 09:09:38 +09001233 ${CHECK_EXCEPTION}
1234 ${RETURN_CLAUSE}
1235}""")
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001236 values = self.GetCalledByNativeValues(called_by_native)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001237 values['FUNCTION_SIGNATURE'] = (
1238 function_signature_template.substitute(values))
1239 if called_by_native.system_class:
1240 values['FUNCTION_HEADER'] = (
1241 function_header_with_unused_template.substitute(values))
1242 else:
1243 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
1244 return template.substitute(values)
1245
1246 def GetKMethodArrayEntry(self, native):
torne94bda722015-02-20 21:37:52 +09001247 template = Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' +
1248 'reinterpret_cast<void*>(${STUB_NAME}) },')
bulach@chromium.org6079a072012-02-24 09:09:38 +09001249 values = {'NAME': native.name,
bulach@chromium.orgc6b75842012-11-01 07:58:15 +09001250 'JNI_SIGNATURE': JniParams.Signature(native.params,
1251 native.return_type,
torne94bda722015-02-20 21:37:52 +09001252 True),
1253 'STUB_NAME': self.GetStubName(native)}
bulach@chromium.org6079a072012-02-24 09:09:38 +09001254 return template.substitute(values)
1255
1256 def GetUniqueClasses(self, origin):
1257 ret = {self.class_name: self.fully_qualified_class}
1258 for entry in origin:
1259 class_name = self.class_name
1260 jni_class_path = self.fully_qualified_class
1261 if entry.java_class_name:
1262 class_name = entry.java_class_name
1263 jni_class_path = self.fully_qualified_class + '$' + class_name
1264 ret[class_name] = jni_class_path
1265 return ret
1266
1267 def GetClassPathDefinitions(self):
1268 """Returns the ClassPath constants."""
1269 ret = []
1270 template = Template("""\
bulach@chromium.org1a8e1552012-03-02 00:51:51 +09001271const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
bulach@chromium.org6079a072012-02-24 09:09:38 +09001272 native_classes = self.GetUniqueClasses(self.natives)
1273 called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001274 if self.options.native_exports:
1275 all_classes = called_by_native_classes
1276 else:
1277 all_classes = native_classes
1278 all_classes.update(called_by_native_classes)
1279
bulach@chromium.org6079a072012-02-24 09:09:38 +09001280 for clazz in all_classes:
1281 values = {
1282 'JAVA_CLASS': clazz,
torne@chromium.org6cbf5b92013-05-29 22:51:23 +09001283 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]),
bulach@chromium.org6079a072012-02-24 09:09:38 +09001284 }
1285 ret += [template.substitute(values)]
1286 ret += ''
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001287
1288 class_getter_methods = []
1289 if self.options.native_exports:
ygorshenin@chromium.org25171e52014-08-20 20:44:05 +09001290 template = Template("""\
1291// Leaking this jclass as we cannot use LazyInstance from some threads.
torne09434e52015-01-09 04:48:31 +09001292base::subtle::AtomicWord g_${JAVA_CLASS}_clazz __attribute__((unused)) = 0;
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001293#define ${JAVA_CLASS}_clazz(env) \
1294base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \
1295&g_${JAVA_CLASS}_clazz)""")
1296 else:
1297 template = Template("""\
1298// Leaking this jclass as we cannot use LazyInstance from some threads.
1299jclass g_${JAVA_CLASS}_clazz = NULL;
1300#define ${JAVA_CLASS}_clazz(env) g_${JAVA_CLASS}_clazz""")
1301
1302 for clazz in called_by_native_classes:
bulach@chromium.org6079a072012-02-24 09:09:38 +09001303 values = {
1304 'JAVA_CLASS': clazz,
1305 }
ygorshenin@chromium.org25171e52014-08-20 20:44:05 +09001306 ret += [template.substitute(values)]
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001307
bulach@chromium.org6079a072012-02-24 09:09:38 +09001308 return '\n'.join(ret)
1309
1310 def GetFindClasses(self):
1311 """Returns the imlementation of FindClass for all known classes."""
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001312 if self.init_native:
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001313 if self.options.native_exports:
1314 template = Template("""\
1315 base::subtle::Release_Store(&g_${JAVA_CLASS}_clazz,
1316 static_cast<base::subtle::AtomicWord>(env->NewWeakGlobalRef(clazz));""")
1317 else:
1318 template = Template("""\
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001319 g_${JAVA_CLASS}_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));""")
1320 else:
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001321 if self.options.native_exports:
1322 return '\n'
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001323 template = Template("""\
bulach@chromium.org1a8e1552012-03-02 00:51:51 +09001324 g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
nileshagrawal@chromium.org118e59a2013-04-09 18:07:13 +09001325 base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""")
bulach@chromium.org6079a072012-02-24 09:09:38 +09001326 ret = []
1327 for clazz in self.GetUniqueClasses(self.called_by_natives):
1328 values = {'JAVA_CLASS': clazz}
1329 ret += [template.substitute(values)]
1330 return '\n'.join(ret)
1331
1332 def GetMethodIDImpl(self, called_by_native):
1333 """Returns the implementation of GetMethodID."""
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001334 if self.options.eager_called_by_natives:
1335 template = Template("""\
1336env->Get${STATIC_METHOD_PART}MethodID(
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001337 ${JAVA_CLASS}_clazz(env),
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001338 "${JNI_NAME}", ${JNI_SIGNATURE});""")
1339 else:
1340 template = Template("""\
bulach@chromium.org231cff62012-10-17 03:35:10 +09001341 base::android::MethodID::LazyGet<
1342 base::android::MethodID::TYPE_${STATIC}>(
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001343 env, ${JAVA_CLASS}_clazz(env),
bulach@chromium.org231cff62012-10-17 03:35:10 +09001344 "${JNI_NAME}",
1345 ${JNI_SIGNATURE},
1346 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
bulach@chromium.org6079a072012-02-24 09:09:38 +09001347""")
bulach@chromium.org31af7532012-09-24 20:01:41 +09001348 jni_name = called_by_native.name
1349 jni_return_type = called_by_native.return_type
1350 if called_by_native.is_constructor:
1351 jni_name = '<init>'
1352 jni_return_type = 'void'
bulach@chromium.org37fc9112013-10-26 01:27:03 +09001353 if called_by_native.signature:
1354 signature = called_by_native.signature
1355 else:
1356 signature = JniParams.Signature(called_by_native.params,
1357 jni_return_type,
1358 True)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001359 values = {
1360 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
bulach@chromium.org31af7532012-09-24 20:01:41 +09001361 'JNI_NAME': jni_name,
bulach@chromium.org6079a072012-02-24 09:09:38 +09001362 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
bulach@chromium.org231cff62012-10-17 03:35:10 +09001363 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001364 'STATIC_METHOD_PART': 'Static' if called_by_native.static else '',
bulach@chromium.org37fc9112013-10-26 01:27:03 +09001365 'JNI_SIGNATURE': signature,
bulach@chromium.org6079a072012-02-24 09:09:38 +09001366 }
1367 return template.substitute(values)
1368
1369
1370def WrapOutput(output):
1371 ret = []
1372 for line in output.splitlines():
tedchoc@chromium.org72992182012-08-03 11:00:17 +09001373 # Do not wrap lines under 80 characters or preprocessor directives.
1374 if len(line) < 80 or line.lstrip()[:1] == '#':
bulach@chromium.org1c775752012-06-22 19:03:16 +09001375 stripped = line.rstrip()
1376 if len(ret) == 0 or len(ret[-1]) or len(stripped):
1377 ret.append(stripped)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001378 else:
1379 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
1380 subsequent_indent = first_line_indent + ' ' * 4
1381 if line.startswith('//'):
1382 subsequent_indent = '//' + subsequent_indent
1383 wrapper = textwrap.TextWrapper(width=80,
1384 subsequent_indent=subsequent_indent,
1385 break_long_words=False)
1386 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
1387 ret += ['']
1388 return '\n'.join(ret)
1389
1390
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001391def ExtractJarInputFile(jar_file, input_file, out_dir):
1392 """Extracts input file from jar and returns the filename.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001393
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001394 The input file is extracted to the same directory that the generated jni
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001395 headers will be placed in. This is passed as an argument to script.
1396
1397 Args:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001398 jar_file: the jar file containing the input files to extract.
1399 input_files: the list of files to extract from the jar file.
1400 out_dir: the name of the directories to extract to.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001401
1402 Returns:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001403 the name of extracted input file.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001404 """
1405 jar_file = zipfile.ZipFile(jar_file)
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001406
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001407 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
torne@chromium.org108c37d2013-03-08 22:52:50 +09001408 try:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001409 os.makedirs(out_dir)
torne@chromium.org108c37d2013-03-08 22:52:50 +09001410 except OSError as e:
1411 if e.errno != errno.EEXIST:
1412 raise
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001413 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
1414 with open(extracted_file_name, 'w') as outfile:
1415 outfile.write(jar_file.read(input_file))
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001416
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001417 return extracted_file_name
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001418
1419
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001420def GenerateJNIHeader(input_file, output_file, options):
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001421 try:
1422 if os.path.splitext(input_file)[1] == '.class':
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001423 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001424 content = jni_from_javap.GetContent()
bulach@chromium.org6079a072012-02-24 09:09:38 +09001425 else:
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001426 jni_from_java_source = JNIFromJavaSource.CreateFromFile(
1427 input_file, options)
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001428 content = jni_from_java_source.GetContent()
1429 except ParseError, e:
1430 print e
1431 sys.exit(1)
1432 if output_file:
1433 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
1434 os.makedirs(os.path.dirname(os.path.abspath(output_file)))
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001435 if options.optimize_generation and os.path.exists(output_file):
tedchoc@chromium.org91b355f2013-02-22 09:37:33 +09001436 with file(output_file, 'r') as f:
1437 existing_content = f.read()
1438 if existing_content == content:
1439 return
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001440 with file(output_file, 'w') as f:
1441 f.write(content)
1442 else:
1443 print output
bulach@chromium.org6079a072012-02-24 09:09:38 +09001444
1445
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001446def GetScriptName():
1447 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
1448 base_index = 0
1449 for idx, value in enumerate(script_components):
1450 if value == 'base' or value == 'third_party':
1451 base_index = idx
1452 break
1453 return os.sep.join(script_components[base_index:])
1454
1455
bulach@chromium.org6079a072012-02-24 09:09:38 +09001456def main(argv):
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001457 usage = """usage: %prog [OPTIONS]
bulach@chromium.org6079a072012-02-24 09:09:38 +09001458This script will parse the given java source code extracting the native
1459declarations and print the header file to stdout (or a file).
1460See SampleForTests.java for more details.
1461 """
1462 option_parser = optparse.OptionParser(usage=usage)
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +09001463 build_utils.AddDepfileOption(option_parser)
1464
cjhopman@chromium.orge607d252014-06-06 04:47:30 +09001465 option_parser.add_option('-j', '--jar_file', dest='jar_file',
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001466 help='Extract the list of input files from'
bulach@chromium.org7f85d462012-05-30 18:56:02 +09001467 ' a specified jar file.'
1468 ' Uses javap to extract the methods from a'
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001469 ' pre-compiled class. --input should point'
bulach@chromium.org6079a072012-02-24 09:09:38 +09001470 ' to pre-compiled Java .class files.')
1471 option_parser.add_option('-n', dest='namespace',
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001472 help='Uses as a namespace in the generated header '
1473 'instead of the javap class name, or when there is '
1474 'no JNINamespace annotation in the java source.')
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001475 option_parser.add_option('--input_file',
1476 help='Single input file name. The output file name '
1477 'will be derived from it. Must be used with '
1478 '--output_dir.')
1479 option_parser.add_option('--output_dir',
1480 help='The output directory. Must be used with '
1481 '--input')
tedchoc@chromium.org91b355f2013-02-22 09:37:33 +09001482 option_parser.add_option('--optimize_generation', type="int",
1483 default=0, help='Whether we should optimize JNI '
1484 'generation by not regenerating files if they have '
1485 'not changed.')
torne@chromium.org6cbf5b92013-05-29 22:51:23 +09001486 option_parser.add_option('--jarjar',
1487 help='Path to optional jarjar rules file.')
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001488 option_parser.add_option('--script_name', default=GetScriptName(),
1489 help='The name of this script in the generated '
1490 'header.')
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001491 option_parser.add_option('--includes',
1492 help='The comma-separated list of header files to '
1493 'include in the generated header.')
1494 option_parser.add_option('--pure_native_methods',
1495 action='store_true', dest='pure_native_methods',
1496 help='When true, the native methods will be called '
1497 'without any JNI-specific arguments.')
bulach@chromium.orga022cf62013-11-05 09:54:22 +09001498 option_parser.add_option('--ptr_type', default='int',
1499 type='choice', choices=['int', 'long'],
1500 help='The type used to represent native pointers in '
1501 'Java code. For 32-bit, use int; '
1502 'for 64-bit, use long.')
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001503 option_parser.add_option('--jni_init_native_name', default='',
1504 help='The name of the JNI registration method that '
1505 'is used to initialize all native methods. If a '
1506 'method with this name is not present in the Java '
1507 'source file, setting this option is a no-op. When '
1508 'a method with this name is found however, the '
1509 'naming convention Java_<packageName>_<className> '
1510 'will limit the initialization to only the '
1511 'top-level class.')
1512 option_parser.add_option('--eager_called_by_natives',
1513 action='store_true', dest='eager_called_by_natives',
1514 help='When true, the called-by-native methods will '
1515 'be initialized in a non-atomic way.')
bulach@chromium.org92d0dac2014-01-17 01:08:05 +09001516 option_parser.add_option('--cpp', default='cpp',
1517 help='The path to cpp command.')
1518 option_parser.add_option('--javap', default='javap',
1519 help='The path to javap command.')
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001520 option_parser.add_option('--native_exports', action='store_true',
1521 help='Native method registration through .so '
1522 'exports.')
torne0dd0b0d2015-02-21 08:06:33 +09001523 option_parser.add_option('--native_exports_optional', action='store_true',
1524 help='Support both explicit and native method'
1525 'registration.')
bulach@chromium.org6079a072012-02-24 09:09:38 +09001526 options, args = option_parser.parse_args(argv)
torne0dd0b0d2015-02-21 08:06:33 +09001527 if options.native_exports_optional:
1528 options.native_exports = True
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001529 if options.jar_file:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001530 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1531 options.output_dir)
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001532 elif options.input_file:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001533 input_file = options.input_file
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001534 else:
1535 option_parser.print_help()
1536 print '\nError: Must specify --jar_file or --input_file.'
1537 return 1
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001538 output_file = None
1539 if options.output_dir:
1540 root_name = os.path.splitext(os.path.basename(input_file))[0]
1541 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
torne@chromium.org6cbf5b92013-05-29 22:51:23 +09001542 if options.jarjar:
1543 with open(options.jarjar) as f:
1544 JniParams.SetJarJarMappings(f.read())
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001545 GenerateJNIHeader(input_file, output_file, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001546
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +09001547 if options.depfile:
1548 build_utils.WriteDepfile(
1549 options.depfile,
1550 build_utils.GetPythonDependencies())
1551
bulach@chromium.org6079a072012-02-24 09:09:38 +09001552
1553if __name__ == '__main__':
1554 sys.exit(main(sys.argv))