blob: 90d0c1cf0c8b204f2f221d9d0ee9d5d0e5aa8656 [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
bulach@chromium.org6079a072012-02-24 09:09:38 +090014from string import Template
15import subprocess
16import sys
17import textwrap
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +090018import zipfile
bulach@chromium.org6079a072012-02-24 09:09:38 +090019
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +090020CHROMIUM_SRC = os.path.join(
21 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
22BUILD_ANDROID_GYP = os.path.join(
23 CHROMIUM_SRC, 'build', 'android', 'gyp')
24
25sys.path.append(BUILD_ANDROID_GYP)
26
27from util import build_utils
28
bulach@chromium.org6079a072012-02-24 09:09:38 +090029
Andrew Grievebe861e02017-08-15 05:08:33 +090030# Match single line comments, multiline comments, character literals, and
31# double-quoted strings.
32_COMMENT_REMOVER_REGEX = re.compile(
33 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
34 re.DOTALL | re.MULTILINE)
35
36_EXTRACT_NATIVES_REGEX = re.compile(
37 r'(@NativeClassQualifiedName'
38 '\(\"(?P<native_class_name>.*?)\"\)\s+)?'
39 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
40 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
41 '(?P<return_type>\S*) '
42 '(?P<name>native\w+)\((?P<params>.*?)\);')
43
44
bulach@chromium.org6079a072012-02-24 09:09:38 +090045class ParseError(Exception):
46 """Exception thrown when we can't parse the input file."""
47
48 def __init__(self, description, *context_lines):
49 Exception.__init__(self)
50 self.description = description
51 self.context_lines = context_lines
52
53 def __str__(self):
54 context = '\n'.join(self.context_lines)
55 return '***\nERROR: %s\n\n%s\n***' % (self.description, context)
56
57
58class Param(object):
59 """Describes a param for a method, either java or native."""
60
61 def __init__(self, **kwargs):
62 self.datatype = kwargs['datatype']
63 self.name = kwargs['name']
bulach@chromium.org6079a072012-02-24 09:09:38 +090064
65
66class NativeMethod(object):
67 """Describes a C/C++ method that is called by Java code"""
68
69 def __init__(self, **kwargs):
70 self.static = kwargs['static']
71 self.java_class_name = kwargs['java_class_name']
72 self.return_type = kwargs['return_type']
73 self.name = kwargs['name']
74 self.params = kwargs['params']
75 if self.params:
76 assert type(self.params) is list
77 assert type(self.params[0]) is Param
78 if (self.params and
bulach@chromium.orga022cf62013-11-05 09:54:22 +090079 self.params[0].datatype == kwargs.get('ptr_type', 'int') and
bulach@chromium.org6079a072012-02-24 09:09:38 +090080 self.params[0].name.startswith('native')):
81 self.type = 'method'
bulach@chromium.org1c775752012-06-22 19:03:16 +090082 self.p0_type = self.params[0].name[len('native'):]
83 if kwargs.get('native_class_name'):
84 self.p0_type = kwargs['native_class_name']
bulach@chromium.org6079a072012-02-24 09:09:38 +090085 else:
86 self.type = 'function'
87 self.method_id_var_name = kwargs.get('method_id_var_name', None)
88
89
90class CalledByNative(object):
91 """Describes a java method exported to c/c++"""
92
93 def __init__(self, **kwargs):
94 self.system_class = kwargs['system_class']
95 self.unchecked = kwargs['unchecked']
96 self.static = kwargs['static']
97 self.java_class_name = kwargs['java_class_name']
98 self.return_type = kwargs['return_type']
bulach@chromium.org6079a072012-02-24 09:09:38 +090099 self.name = kwargs['name']
100 self.params = kwargs['params']
101 self.method_id_var_name = kwargs.get('method_id_var_name', None)
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900102 self.signature = kwargs.get('signature')
bulach@chromium.org31af7532012-09-24 20:01:41 +0900103 self.is_constructor = kwargs.get('is_constructor', False)
104 self.env_call = GetEnvCall(self.is_constructor, self.static,
105 self.return_type)
106 self.static_cast = GetStaticCastForReturnType(self.return_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900107
108
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900109class ConstantField(object):
110 def __init__(self, **kwargs):
111 self.name = kwargs['name']
112 self.value = kwargs['value']
113
114
bulach@chromium.org6079a072012-02-24 09:09:38 +0900115def JavaDataTypeToC(java_type):
116 """Returns a C datatype for the given java type."""
117 java_pod_type_map = {
118 'int': 'jint',
119 'byte': 'jbyte',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900120 'char': 'jchar',
121 'short': 'jshort',
bulach@chromium.org6079a072012-02-24 09:09:38 +0900122 'boolean': 'jboolean',
123 'long': 'jlong',
124 'double': 'jdouble',
125 'float': 'jfloat',
126 }
127 java_type_map = {
128 'void': 'void',
129 'String': 'jstring',
Torne (Richard Coles)c05b69d2018-02-16 04:23:34 +0900130 'Class': 'jclass',
tornec9124232015-08-27 23:57:06 +0900131 'Throwable': 'jthrowable',
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900132 'java/lang/String': 'jstring',
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900133 'java/lang/Class': 'jclass',
tornec9124232015-08-27 23:57:06 +0900134 'java/lang/Throwable': 'jthrowable',
bulach@chromium.org6079a072012-02-24 09:09:38 +0900135 }
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900136
Torne (Richard Coles)c05b69d2018-02-16 04:23:34 +0900137 java_type = _StripGenerics(java_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900138 if java_type in java_pod_type_map:
139 return java_pod_type_map[java_type]
140 elif java_type in java_type_map:
141 return java_type_map[java_type]
142 elif java_type.endswith('[]'):
143 if java_type[:-2] in java_pod_type_map:
144 return java_pod_type_map[java_type[:-2]] + 'Array'
145 return 'jobjectArray'
146 else:
147 return 'jobject'
148
149
tornec510c592015-09-04 20:16:35 +0900150def WrapCTypeForDeclaration(c_type):
151 """Wrap the C datatype in a JavaRef if required."""
152 if re.match(RE_SCOPED_JNI_TYPES, c_type):
torne9854c722016-08-05 00:44:06 +0900153 return 'const base::android::JavaParamRef<' + c_type + '>&'
tornec510c592015-09-04 20:16:35 +0900154 else:
155 return c_type
156
157
Andrew Grievebe861e02017-08-15 05:08:33 +0900158def _JavaDataTypeToCForDeclaration(java_type):
tornec510c592015-09-04 20:16:35 +0900159 """Returns a JavaRef-wrapped C datatype for the given java type."""
160 return WrapCTypeForDeclaration(JavaDataTypeToC(java_type))
161
162
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900163def JavaDataTypeToCForCalledByNativeParam(java_type):
164 """Returns a C datatype to be when calling from native."""
165 if java_type == 'int':
166 return 'JniIntWrapper'
167 else:
torne28061e92016-08-09 01:29:18 +0900168 c_type = JavaDataTypeToC(java_type)
169 if re.match(RE_SCOPED_JNI_TYPES, c_type):
Torne (Richard Coles)819c0562017-08-16 05:04:57 +0900170 return 'const base::android::JavaRef<' + c_type + '>&'
torne28061e92016-08-09 01:29:18 +0900171 else:
172 return c_type
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900173
174
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900175def JavaReturnValueToC(java_type):
176 """Returns a valid C return value for the given java type."""
177 java_pod_type_map = {
178 'int': '0',
179 'byte': '0',
180 'char': '0',
181 'short': '0',
182 'boolean': 'false',
183 'long': '0',
184 'double': '0',
185 'float': '0',
186 'void': ''
187 }
188 return java_pod_type_map.get(java_type, 'NULL')
189
190
Andrew Grievebe861e02017-08-15 05:08:33 +0900191def _GetJNIFirstParamType(native):
192 if native.type == 'function' and native.static:
193 return 'jclass'
194 return 'jobject'
195
196
197def _GetJNIFirstParam(native, for_declaration):
198 c_type = _GetJNIFirstParamType(native)
199 if for_declaration:
200 c_type = WrapCTypeForDeclaration(c_type)
201 return [c_type + ' jcaller']
202
203
204def _GetParamsInDeclaration(native):
205 """Returns the params for the forward declaration.
206
207 Args:
208 native: the native dictionary describing the method.
209
210 Returns:
211 A string containing the params.
212 """
213 return ',\n '.join(_GetJNIFirstParam(native, True) +
214 [_JavaDataTypeToCForDeclaration(param.datatype) + ' ' +
215 param.name
216 for param in native.params])
217
218
219def GetParamsInStub(native):
220 """Returns the params for the stub declaration.
221
222 Args:
223 native: the native dictionary describing the method.
224
225 Returns:
226 A string containing the params.
227 """
228 return ',\n '.join(_GetJNIFirstParam(native, False) +
229 [JavaDataTypeToC(param.datatype) + ' ' +
230 param.name
231 for param in native.params])
232
233
Sami Kalliomäki7e2954d2017-12-01 02:15:32 +0900234def _StripGenerics(value):
235 """Strips Java generics from a string."""
236 nest_level = 0 # How deeply we are nested inside the generics.
237 start_index = 0 # Starting index of the last non-generic region.
238 out = []
239
240 for i, c in enumerate(value):
241 if c == '<':
242 if nest_level == 0:
243 out.append(value[start_index:i])
244 nest_level += 1
245 elif c == '>':
246 start_index = i + 1
247 nest_level -= 1
248 out.append(value[start_index:])
249
250 return ''.join(out)
251
252
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900253class JniParams(object):
Yipeng Wang60438ac2017-06-17 06:08:33 +0900254 """Get JNI related parameters."""
bulach@chromium.org6079a072012-02-24 09:09:38 +0900255
Yipeng Wang60438ac2017-06-17 06:08:33 +0900256 def __init__(self, fully_qualified_class):
257 self._fully_qualified_class = 'L' + fully_qualified_class
258 self._package = '/'.join(fully_qualified_class.split('/')[:-1])
259 self._imports = []
260 self._inner_classes = []
261 self._implicit_imports = []
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900262
Yipeng Wang60438ac2017-06-17 06:08:33 +0900263 def ExtractImportsAndInnerClasses(self, contents):
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900264 contents = contents.replace('\n', '')
265 re_import = re.compile(r'import.*?(?P<class>\S*?);')
266 for match in re.finditer(re_import, contents):
Yipeng Wang60438ac2017-06-17 06:08:33 +0900267 self._imports += ['L' + match.group('class').replace('.', '/')]
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900268
Magnus Jedvert2cc57862017-11-22 03:49:54 +0900269 re_inner = re.compile(r'(class|interface|enum)\s+?(?P<name>\w+?)\W')
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900270 for match in re.finditer(re_inner, contents):
271 inner = match.group('name')
Yipeng Wang60438ac2017-06-17 06:08:33 +0900272 if not self._fully_qualified_class.endswith(inner):
273 self._inner_classes += [self._fully_qualified_class + '$' +
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900274 inner]
bulach@chromium.org6079a072012-02-24 09:09:38 +0900275
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900276 re_additional_imports = re.compile(
qsr@chromium.org74f541e2014-05-23 23:44:37 +0900277 r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900278 for match in re.finditer(re_additional_imports, contents):
qsr@chromium.org74f541e2014-05-23 23:44:37 +0900279 for class_name in match.group('class_names').split(','):
Yipeng Wang60438ac2017-06-17 06:08:33 +0900280 self._AddAdditionalImport(class_name.strip())
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900281
Yipeng Wang60438ac2017-06-17 06:08:33 +0900282 def JavaToJni(self, param):
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900283 """Converts a java param into a JNI signature type."""
284 pod_param_map = {
285 'int': 'I',
286 'boolean': 'Z',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900287 'char': 'C',
288 'short': 'S',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900289 'long': 'J',
290 'double': 'D',
291 'float': 'F',
292 'byte': 'B',
293 'void': 'V',
294 }
295 object_param_list = [
296 'Ljava/lang/Boolean',
297 'Ljava/lang/Integer',
298 'Ljava/lang/Long',
299 'Ljava/lang/Object',
300 'Ljava/lang/String',
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900301 'Ljava/lang/Class',
aurimas9e29f732015-06-25 07:53:10 +0900302 'Ljava/lang/CharSequence',
303 'Ljava/lang/Runnable',
304 'Ljava/lang/Throwable',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900305 ]
bulach@chromium.org69931302014-05-08 04:16:23 +0900306
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900307 prefix = ''
308 # Array?
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900309 while param[-2:] == '[]':
310 prefix += '['
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900311 param = param[:-2]
312 # Generic?
313 if '<' in param:
314 param = param[:param.index('<')]
315 if param in pod_param_map:
316 return prefix + pod_param_map[param]
317 if '/' in param:
318 # Coming from javap, use the fully qualified param directly.
torneb66773d2016-05-10 02:08:20 +0900319 return prefix + 'L' + param + ';'
bulach@chromium.org4a130cc2014-03-28 20:15:06 +0900320
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900321 for qualified_name in (object_param_list +
Yipeng Wang60438ac2017-06-17 06:08:33 +0900322 [self._fully_qualified_class] + self._inner_classes):
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900323 if (qualified_name.endswith('/' + param) or
324 qualified_name.endswith('$' + param.replace('.', '$')) or
325 qualified_name == 'L' + param):
torneb66773d2016-05-10 02:08:20 +0900326 return prefix + qualified_name + ';'
bulach@chromium.org7156c762012-11-13 23:35:00 +0900327
328 # Is it from an import? (e.g. referecing Class from import pkg.Class;
329 # note that referencing an inner class Inner from import pkg.Class.Inner
330 # is not supported).
Yipeng Wang60438ac2017-06-17 06:08:33 +0900331 for qualified_name in self._imports:
bulach@chromium.org7156c762012-11-13 23:35:00 +0900332 if qualified_name.endswith('/' + param):
333 # Ensure it's not an inner class.
334 components = qualified_name.split('/')
335 if len(components) > 2 and components[-2][0].isupper():
336 raise SyntaxError('Inner class (%s) can not be imported '
337 'and used by JNI (%s). Please import the outer '
338 'class and use Outer.Inner instead.' %
339 (qualified_name, param))
torneb66773d2016-05-10 02:08:20 +0900340 return prefix + qualified_name + ';'
bulach@chromium.org7156c762012-11-13 23:35:00 +0900341
342 # Is it an inner class from an outer class import? (e.g. referencing
343 # Class.Inner from import pkg.Class).
344 if '.' in param:
345 components = param.split('.')
346 outer = '/'.join(components[:-1])
347 inner = components[-1]
Yipeng Wang60438ac2017-06-17 06:08:33 +0900348 for qualified_name in self._imports:
bulach@chromium.org7156c762012-11-13 23:35:00 +0900349 if qualified_name.endswith('/' + outer):
torneb66773d2016-05-10 02:08:20 +0900350 return (prefix + qualified_name + '$' + inner + ';')
bulach@chromium.org4a130cc2014-03-28 20:15:06 +0900351 raise SyntaxError('Inner class (%s) can not be '
352 'used directly by JNI. Please import the outer '
353 'class, probably:\n'
354 'import %s.%s;' %
Yipeng Wang60438ac2017-06-17 06:08:33 +0900355 (param, self._package.replace('/', '.'),
bulach@chromium.org4a130cc2014-03-28 20:15:06 +0900356 outer.replace('/', '.')))
bulach@chromium.org7156c762012-11-13 23:35:00 +0900357
Yipeng Wang60438ac2017-06-17 06:08:33 +0900358 self._CheckImplicitImports(param)
bulach@chromium.org69931302014-05-08 04:16:23 +0900359
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900360 # Type not found, falling back to same package as this class.
Yipeng Wang60438ac2017-06-17 06:08:33 +0900361 return (prefix + 'L' + self._package + '/' + param + ';')
bulach@chromium.org6079a072012-02-24 09:09:38 +0900362
Yipeng Wang60438ac2017-06-17 06:08:33 +0900363 def _AddAdditionalImport(self, class_name):
364 assert class_name.endswith('.class')
365 raw_class_name = class_name[:-len('.class')]
366 if '.' in raw_class_name:
367 raise SyntaxError('%s cannot be used in @JNIAdditionalImport. '
368 'Only import unqualified outer classes.' % class_name)
369 new_import = 'L%s/%s' % (self._package, raw_class_name)
370 if new_import in self._imports:
371 raise SyntaxError('Do not use JNIAdditionalImport on an already '
372 'imported class: %s' % (new_import.replace('/', '.')))
373 self._imports += [new_import]
374
375 def _CheckImplicitImports(self, param):
bulach@chromium.org69931302014-05-08 04:16:23 +0900376 # Ensure implicit imports, such as java.lang.*, are not being treated
377 # as being in the same package.
Yipeng Wang60438ac2017-06-17 06:08:33 +0900378 if not self._implicit_imports:
bulach@chromium.org69931302014-05-08 04:16:23 +0900379 # This file was generated from android.jar and lists
380 # all classes that are implicitly imported.
381 with file(os.path.join(os.path.dirname(sys.argv[0]),
382 'android_jar.classes'), 'r') as f:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900383 self._implicit_imports = f.readlines()
384 for implicit_import in self._implicit_imports:
bulach@chromium.org69931302014-05-08 04:16:23 +0900385 implicit_import = implicit_import.strip().replace('.class', '')
386 implicit_import = implicit_import.replace('/', '.')
387 if implicit_import.endswith('.' + param):
388 raise SyntaxError('Ambiguous class (%s) can not be used directly '
389 'by JNI.\nPlease import it, probably:\n\n'
390 'import %s;' %
391 (param, implicit_import))
392
Yipeng Wang60438ac2017-06-17 06:08:33 +0900393 def Signature(self, params, returns, wrap):
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900394 """Returns the JNI signature for the given datatypes."""
395 items = ['(']
Yipeng Wang60438ac2017-06-17 06:08:33 +0900396 items += [self.JavaToJni(param.datatype) for param in params]
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900397 items += [')']
Yipeng Wang60438ac2017-06-17 06:08:33 +0900398 items += [self.JavaToJni(returns)]
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900399 if wrap:
400 return '\n' + '\n'.join(['"' + item + '"' for item in items])
401 else:
402 return '"' + ''.join(items) + '"'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900403
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900404 @staticmethod
Yipeng Wang60438ac2017-06-17 06:08:33 +0900405 def ParseJavaPSignature(signature_line):
406 prefix = 'Signature: '
407 index = signature_line.find(prefix)
408 if index == -1:
409 prefix = 'descriptor: '
410 index = signature_line.index(prefix)
411 return '"%s"' % signature_line[index + len(prefix):]
412
413 @staticmethod
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900414 def Parse(params):
415 """Parses the params into a list of Param objects."""
416 if not params:
417 return []
418 ret = []
Sami Kalliomäki7e2954d2017-12-01 02:15:32 +0900419 params = _StripGenerics(params)
420 for p in params.split(','):
421 items = p.split()
bauerbb7c53862016-08-25 22:41:41 +0900422
423 # Remove @Annotations from parameters.
424 while items[0].startswith('@'):
425 del items[0]
426
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900427 if 'final' in items:
428 items.remove('final')
bauerbb7c53862016-08-25 22:41:41 +0900429
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900430 param = Param(
431 datatype=items[0],
432 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
433 )
434 ret += [param]
435 return ret
bulach@chromium.org6079a072012-02-24 09:09:38 +0900436
bulach@chromium.org6079a072012-02-24 09:09:38 +0900437
bulach@chromium.org1c775752012-06-22 19:03:16 +0900438def ExtractJNINamespace(contents):
439 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
440 m = re.findall(re_jni_namespace, contents)
441 if not m:
442 return ''
443 return m[0]
444
445
bulach@chromium.org6079a072012-02-24 09:09:38 +0900446def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
447 re_package = re.compile('.*?package (.*?);')
448 matches = re.findall(re_package, contents)
449 if not matches:
450 raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
451 return (matches[0].replace('.', '/') + '/' +
452 os.path.splitext(os.path.basename(java_file_name))[0])
453
454
bulach@chromium.orga022cf62013-11-05 09:54:22 +0900455def ExtractNatives(contents, ptr_type):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900456 """Returns a list of dict containing information about a native method."""
457 contents = contents.replace('\n', '')
458 natives = []
Andrew Grievebe861e02017-08-15 05:08:33 +0900459 for match in _EXTRACT_NATIVES_REGEX.finditer(contents):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900460 native = NativeMethod(
bulach@chromium.org1c775752012-06-22 19:03:16 +0900461 static='static' in match.group('qualifiers'),
462 java_class_name=match.group('java_class_name'),
463 native_class_name=match.group('native_class_name'),
bulach@chromium.orga6e185e2013-03-26 16:32:39 +0900464 return_type=match.group('return_type'),
bulach@chromium.org1c775752012-06-22 19:03:16 +0900465 name=match.group('name').replace('native', ''),
bulach@chromium.orga022cf62013-11-05 09:54:22 +0900466 params=JniParams.Parse(match.group('params')),
467 ptr_type=ptr_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900468 natives += [native]
469 return natives
470
471
estevenson2498e5d2017-02-01 05:34:56 +0900472def IsMainDexJavaClass(contents):
Yipeng Wangfba87a32017-07-01 03:16:41 +0900473 """Returns True if the class is annotated with "@MainDex", False if not.
estevenson2498e5d2017-02-01 05:34:56 +0900474
475 JNI registration doesn't always need to be completed for non-browser processes
476 since most Java code is only used by the browser process. Classes that are
477 needed by non-browser processes must explicitly be annotated with @MainDex
478 to force JNI registration.
479 """
Jay Civelli76455192017-06-10 06:57:19 +0900480 re_maindex = re.compile(r'@MainDex[\s\S]*class({|[\s\S]*{)')
Yipeng Wangfba87a32017-07-01 03:16:41 +0900481 return bool(re.search(re_maindex, contents))
482
483
484def GetBinaryClassName(fully_qualified_class):
485 """Returns a string concatenating the Java package and class."""
Andrew Grievee4cfd242017-07-29 04:47:28 +0900486 escaped = fully_qualified_class.replace('_', '_1')
487 return escaped.replace('/', '_').replace('$', '_00024')
Yipeng Wangfba87a32017-07-01 03:16:41 +0900488
489
490def GetRegistrationFunctionName(fully_qualified_class):
491 """Returns the register name with a given class."""
492 return 'RegisterNative_' + GetBinaryClassName(fully_qualified_class)
estevenson2498e5d2017-02-01 05:34:56 +0900493
494
bulach@chromium.org31af7532012-09-24 20:01:41 +0900495def GetStaticCastForReturnType(return_type):
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900496 type_map = { 'String' : 'jstring',
497 'java/lang/String' : 'jstring',
Torne (Richard Coles)c05b69d2018-02-16 04:23:34 +0900498 'Class': 'jclass',
499 'java/lang/Class': 'jclass',
tornec9124232015-08-27 23:57:06 +0900500 'Throwable': 'jthrowable',
501 'java/lang/Throwable': 'jthrowable',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900502 'boolean[]': 'jbooleanArray',
503 'byte[]': 'jbyteArray',
504 'char[]': 'jcharArray',
505 'short[]': 'jshortArray',
506 'int[]': 'jintArray',
507 'long[]': 'jlongArray',
skhatri@nvidia.comf5bf8c42014-06-02 21:23:12 +0900508 'float[]': 'jfloatArray',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900509 'double[]': 'jdoubleArray' }
Torne (Richard Coles)c05b69d2018-02-16 04:23:34 +0900510 return_type = _StripGenerics(return_type)
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900511 ret = type_map.get(return_type, None)
512 if ret:
513 return ret
514 if return_type.endswith('[]'):
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900515 return 'jobjectArray'
bulach@chromium.org31af7532012-09-24 20:01:41 +0900516 return None
517
518
519def GetEnvCall(is_constructor, is_static, return_type):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900520 """Maps the types availabe via env->Call__Method."""
bulach@chromium.org31af7532012-09-24 20:01:41 +0900521 if is_constructor:
522 return 'NewObject'
523 env_call_map = {'boolean': 'Boolean',
524 'byte': 'Byte',
525 'char': 'Char',
526 'short': 'Short',
527 'int': 'Int',
528 'long': 'Long',
529 'float': 'Float',
530 'void': 'Void',
531 'double': 'Double',
532 'Object': 'Object',
bulach@chromium.org6079a072012-02-24 09:09:38 +0900533 }
bulach@chromium.org31af7532012-09-24 20:01:41 +0900534 call = env_call_map.get(return_type, 'Object')
535 if is_static:
536 call = 'Static' + call
537 return 'Call' + call + 'Method'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900538
539
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900540def GetMangledParam(datatype):
541 """Returns a mangled identifier for the datatype."""
542 if len(datatype) <= 2:
543 return datatype.replace('[', 'A')
544 ret = ''
545 for i in range(1, len(datatype)):
546 c = datatype[i]
547 if c == '[':
548 ret += 'A'
549 elif c.isupper() or datatype[i - 1] in ['/', 'L']:
550 ret += c.upper()
551 return ret
552
553
Yipeng Wang60438ac2017-06-17 06:08:33 +0900554def GetMangledMethodName(jni_params, name, params, return_type):
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900555 """Returns a mangled method name for the given signature.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900556
557 The returned name can be used as a C identifier and will be unique for all
558 valid overloads of the same method.
559
560 Args:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900561 jni_params: JniParams object.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900562 name: string.
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900563 params: list of Param.
564 return_type: string.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900565
566 Returns:
567 A mangled name.
568 """
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900569 mangled_items = []
570 for datatype in [return_type] + [x.datatype for x in params]:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900571 mangled_items += [GetMangledParam(jni_params.JavaToJni(datatype))]
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900572 mangled_name = name + '_'.join(mangled_items)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900573 assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
574 return mangled_name
575
576
Yipeng Wang60438ac2017-06-17 06:08:33 +0900577def MangleCalledByNatives(jni_params, called_by_natives):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900578 """Mangles all the overloads from the call_by_natives list."""
579 method_counts = collections.defaultdict(
580 lambda: collections.defaultdict(lambda: 0))
581 for called_by_native in called_by_natives:
582 java_class_name = called_by_native.java_class_name
583 name = called_by_native.name
584 method_counts[java_class_name][name] += 1
585 for called_by_native in called_by_natives:
586 java_class_name = called_by_native.java_class_name
587 method_name = called_by_native.name
588 method_id_var_name = method_name
589 if method_counts[java_class_name][method_name] > 1:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900590 method_id_var_name = GetMangledMethodName(jni_params, method_name,
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900591 called_by_native.params,
592 called_by_native.return_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900593 called_by_native.method_id_var_name = method_id_var_name
594 return called_by_natives
595
596
tornec510c592015-09-04 20:16:35 +0900597# Regex to match the JNI types that should be wrapped in a JavaRef.
598RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array')
599
bulach@chromium.org6079a072012-02-24 09:09:38 +0900600
601# Regex to match a string like "@CalledByNative public void foo(int bar)".
602RE_CALLED_BY_NATIVE = re.compile(
Andrew Grievea3848672018-02-22 02:51:40 +0900603 r'@CalledByNative(?P<Unchecked>(?:Unchecked)?)(?:\("(?P<annotation>.*)"\))?'
604 r'(?:\s+@\w+(?:\(.*\))?)*' # Ignore any other annotations.
605 r'\s+(?P<prefix>('
606 r'(private|protected|public|static|abstract|final|default|synchronized)'
607 r'\s*)*)'
608 r'(?:\s*@\w+)?' # Ignore annotations in return types.
609 r'\s*(?P<return_type>\S*?)'
610 r'\s*(?P<name>\w+)'
611 r'\s*\((?P<params>[^\)]*)\)')
bulach@chromium.org6079a072012-02-24 09:09:38 +0900612
dskibab7cdc892016-10-26 08:39:11 +0900613# Removes empty lines that are indented (i.e. start with 2x spaces).
614def RemoveIndentedEmptyLines(string):
615 return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE)
616
617
Yipeng Wang60438ac2017-06-17 06:08:33 +0900618def ExtractCalledByNatives(jni_params, contents):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900619 """Parses all methods annotated with @CalledByNative.
620
621 Args:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900622 jni_params: JniParams object.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900623 contents: the contents of the java file.
624
625 Returns:
626 A list of dict with information about the annotated methods.
627 TODO(bulach): return a CalledByNative object.
628
629 Raises:
630 ParseError: if unable to parse.
631 """
632 called_by_natives = []
633 for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
Magnus Jedvert5b844152017-11-22 06:30:17 +0900634 return_type = match.group('return_type')
635 name = match.group('name')
636 if not return_type:
637 is_constructor = True
638 return_type = name
639 name = "Constructor"
640 else:
641 is_constructor = False
642
bulach@chromium.org6079a072012-02-24 09:09:38 +0900643 called_by_natives += [CalledByNative(
644 system_class=False,
645 unchecked='Unchecked' in match.group('Unchecked'),
646 static='static' in match.group('prefix'),
647 java_class_name=match.group('annotation') or '',
Magnus Jedvert5b844152017-11-22 06:30:17 +0900648 return_type=return_type,
649 name=name,
650 is_constructor=is_constructor,
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900651 params=JniParams.Parse(match.group('params')))]
bulach@chromium.org6079a072012-02-24 09:09:38 +0900652 # Check for any @CalledByNative occurrences that weren't matched.
653 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
654 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
655 if '@CalledByNative' in line1:
656 raise ParseError('could not parse @CalledByNative method signature',
657 line1, line2)
Yipeng Wang60438ac2017-06-17 06:08:33 +0900658 return MangleCalledByNatives(jni_params, called_by_natives)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900659
660
Andrew Grievebe861e02017-08-15 05:08:33 +0900661def RemoveComments(contents):
662 # We need to support both inline and block comments, and we need to handle
663 # strings that contain '//' or '/*'.
664 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
665 # parser. Maybe we could ditch JNIFromJavaSource and just always use
666 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
667 # http://code.google.com/p/chromium/issues/detail?id=138941
668 def replacer(match):
669 # Replace matches that are comments with nothing; return literals/strings
670 # unchanged.
671 s = match.group(0)
672 if s.startswith('/'):
673 return ''
674 else:
675 return s
676 return _COMMENT_REMOVER_REGEX.sub(replacer, contents)
677
678
bulach@chromium.org6079a072012-02-24 09:09:38 +0900679class JNIFromJavaP(object):
680 """Uses 'javap' to parse a .class file and generate the JNI header file."""
681
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900682 def __init__(self, contents, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900683 self.contents = contents
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900684 self.namespace = options.namespace
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900685 for line in contents:
686 class_name = re.match(
687 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
688 line)
689 if class_name:
690 self.fully_qualified_class = class_name.group('class_name')
691 break
bulach@chromium.org6079a072012-02-24 09:09:38 +0900692 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
simonb@opera.com521714e2013-09-04 06:22:48 +0900693 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
694 # away the <...> and use the raw class name that Java 6 would've given us.
695 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
Yipeng Wang60438ac2017-06-17 06:08:33 +0900696 self.jni_params = JniParams(self.fully_qualified_class)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900697 self.java_class_name = self.fully_qualified_class.split('/')[-1]
698 if not self.namespace:
699 self.namespace = 'JNI_' + self.java_class_name
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900700 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
bulach@chromium.org31af7532012-09-24 20:01:41 +0900701 '\((?P<params>.*?)\)')
bulach@chromium.org6079a072012-02-24 09:09:38 +0900702 self.called_by_natives = []
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900703 for lineno, content in enumerate(contents[2:], 2):
bulach@chromium.org31af7532012-09-24 20:01:41 +0900704 match = re.match(re_method, content)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900705 if not match:
706 continue
707 self.called_by_natives += [CalledByNative(
708 system_class=True,
709 unchecked=False,
bulach@chromium.org31af7532012-09-24 20:01:41 +0900710 static='static' in match.group('prefix'),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900711 java_class_name='',
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900712 return_type=match.group('return_type').replace('.', '/'),
bulach@chromium.org31af7532012-09-24 20:01:41 +0900713 name=match.group('name'),
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900714 params=JniParams.Parse(match.group('params').replace('.', '/')),
715 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
716 re_constructor = re.compile('(.*?)public ' +
bulach@chromium.org31af7532012-09-24 20:01:41 +0900717 self.fully_qualified_class.replace('/', '.') +
718 '\((?P<params>.*?)\)')
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900719 for lineno, content in enumerate(contents[2:], 2):
bulach@chromium.org31af7532012-09-24 20:01:41 +0900720 match = re.match(re_constructor, content)
721 if not match:
722 continue
723 self.called_by_natives += [CalledByNative(
bulach@chromium.org208f05b2012-10-03 06:13:55 +0900724 system_class=True,
bulach@chromium.org31af7532012-09-24 20:01:41 +0900725 unchecked=False,
726 static=False,
727 java_class_name='',
728 return_type=self.fully_qualified_class,
729 name='Constructor',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900730 params=JniParams.Parse(match.group('params').replace('.', '/')),
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900731 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
bulach@chromium.org31af7532012-09-24 20:01:41 +0900732 is_constructor=True)]
Yipeng Wang60438ac2017-06-17 06:08:33 +0900733 self.called_by_natives = MangleCalledByNatives(self.jni_params,
734 self.called_by_natives)
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900735 self.constant_fields = []
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900736 re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900737 re_constant_field_value = re.compile(
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900738 '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)')
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900739 for lineno, content in enumerate(contents[2:], 2):
740 match = re.match(re_constant_field, content)
741 if not match:
742 continue
743 value = re.match(re_constant_field_value, contents[lineno + 2])
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900744 if not value:
745 value = re.match(re_constant_field_value, contents[lineno + 3])
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900746 if value:
747 self.constant_fields.append(
748 ConstantField(name=match.group('name'),
749 value=value.group('value')))
750
bulach@chromium.org6079a072012-02-24 09:09:38 +0900751 self.inl_header_file_generator = InlHeaderFileGenerator(
estevenson2498e5d2017-02-01 05:34:56 +0900752 self.namespace, self.fully_qualified_class, [], self.called_by_natives,
Yipeng Wang60438ac2017-06-17 06:08:33 +0900753 self.constant_fields, self.jni_params, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900754
755 def GetContent(self):
756 return self.inl_header_file_generator.GetContent()
757
758 @staticmethod
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900759 def CreateFromClass(class_file, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900760 class_name = os.path.splitext(os.path.basename(class_file))[0]
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900761 p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
762 '-s', class_name],
bulach@chromium.org6079a072012-02-24 09:09:38 +0900763 cwd=os.path.dirname(class_file),
764 stdout=subprocess.PIPE,
765 stderr=subprocess.PIPE)
766 stdout, _ = p.communicate()
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900767 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900768 return jni_from_javap
769
770
771class JNIFromJavaSource(object):
772 """Uses the given java source file to generate the JNI header file."""
773
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900774 def __init__(self, contents, fully_qualified_class, options):
Andrew Grievebe861e02017-08-15 05:08:33 +0900775 contents = RemoveComments(contents)
Yipeng Wang60438ac2017-06-17 06:08:33 +0900776 self.jni_params = JniParams(fully_qualified_class)
777 self.jni_params.ExtractImportsAndInnerClasses(contents)
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900778 jni_namespace = ExtractJNINamespace(contents) or options.namespace
bulach@chromium.orga022cf62013-11-05 09:54:22 +0900779 natives = ExtractNatives(contents, options.ptr_type)
Yipeng Wang60438ac2017-06-17 06:08:33 +0900780 called_by_natives = ExtractCalledByNatives(self.jni_params, contents)
bulach@chromium.orge2530d02012-07-03 01:23:35 +0900781 if len(natives) == 0 and len(called_by_natives) == 0:
782 raise SyntaxError('Unable to find any JNI methods for %s.' %
783 fully_qualified_class)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900784 inl_header_file_generator = InlHeaderFileGenerator(
estevenson2498e5d2017-02-01 05:34:56 +0900785 jni_namespace, fully_qualified_class, natives, called_by_natives, [],
Yipeng Wangfba87a32017-07-01 03:16:41 +0900786 self.jni_params, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900787 self.content = inl_header_file_generator.GetContent()
788
bulach@chromium.org6079a072012-02-24 09:09:38 +0900789 def GetContent(self):
790 return self.content
791
792 @staticmethod
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900793 def CreateFromFile(java_file_name, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900794 contents = file(java_file_name).read()
795 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
796 contents)
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900797 return JNIFromJavaSource(contents, fully_qualified_class, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900798
799
Andrew Grievebe861e02017-08-15 05:08:33 +0900800class HeaderFileGeneratorHelper(object):
801 """Include helper methods for header generators."""
802
803 def __init__(self, class_name, fully_qualified_class):
804 self.class_name = class_name
805 self.fully_qualified_class = fully_qualified_class
806
807 def GetStubName(self, native):
808 """Return the name of the stub function for this native method.
809
810 Args:
811 native: the native dictionary describing the method.
812
813 Returns:
814 A string with the stub function name (used by the JVM).
815 """
816 template = Template("Java_${JAVA_NAME}_native${NAME}")
817
818 java_name = self.fully_qualified_class
819 if native.java_class_name:
820 java_name += '$' + native.java_class_name
821
822 values = {'NAME': native.name,
823 'JAVA_NAME': GetBinaryClassName(java_name)}
824 return template.substitute(values)
825
826 def GetUniqueClasses(self, origin):
827 ret = {self.class_name: self.fully_qualified_class}
828 for entry in origin:
829 class_name = self.class_name
830 jni_class_path = self.fully_qualified_class
831 if entry.java_class_name:
832 class_name = entry.java_class_name
833 jni_class_path = self.fully_qualified_class + '$' + class_name
834 ret[class_name] = jni_class_path
835 return ret
836
837 def GetClassPathLines(self, classes, declare_only=False):
838 """Returns the ClassPath constants."""
839 ret = []
840 if declare_only:
841 template = Template("""\
842extern const char kClassPath_${JAVA_CLASS}[];
843""")
844 else:
845 template = Template("""\
846JNI_REGISTRATION_EXPORT extern const char kClassPath_${JAVA_CLASS}[];
847const char kClassPath_${JAVA_CLASS}[] = \
848"${JNI_CLASS_PATH}";
849""")
850
851 for full_clazz in classes.itervalues():
852 values = {
853 'JAVA_CLASS': GetBinaryClassName(full_clazz),
854 'JNI_CLASS_PATH': full_clazz,
855 }
856 ret += [template.substitute(values)]
857
858 class_getter = """\
859#ifndef ${JAVA_CLASS}_clazz_defined
860#define ${JAVA_CLASS}_clazz_defined
861inline jclass ${JAVA_CLASS}_clazz(JNIEnv* env) {
862 return base::android::LazyGetClass(env, kClassPath_${JAVA_CLASS}, \
863&g_${JAVA_CLASS}_clazz);
864}
865#endif
866"""
867 if declare_only:
868 template = Template("""\
869extern base::subtle::AtomicWord g_${JAVA_CLASS}_clazz;
870""" + class_getter)
871 else:
872 template = Template("""\
873// Leaking this jclass as we cannot use LazyInstance from some threads.
874JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_${JAVA_CLASS}_clazz = 0;
875""" + class_getter)
876
877 for full_clazz in classes.itervalues():
878 values = {
879 'JAVA_CLASS': GetBinaryClassName(full_clazz),
880 }
881 ret += [template.substitute(values)]
882
883 return '\n'.join(ret)
884
885
bulach@chromium.org6079a072012-02-24 09:09:38 +0900886class InlHeaderFileGenerator(object):
887 """Generates an inline header file for JNI integration."""
888
889 def __init__(self, namespace, fully_qualified_class, natives,
Yipeng Wangfba87a32017-07-01 03:16:41 +0900890 called_by_natives, constant_fields, jni_params, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900891 self.namespace = namespace
892 self.fully_qualified_class = fully_qualified_class
893 self.class_name = self.fully_qualified_class.split('/')[-1]
894 self.natives = natives
895 self.called_by_natives = called_by_natives
896 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900897 self.constant_fields = constant_fields
Yipeng Wang60438ac2017-06-17 06:08:33 +0900898 self.jni_params = jni_params
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900899 self.options = options
Andrew Grievebe861e02017-08-15 05:08:33 +0900900 self.helper = HeaderFileGeneratorHelper(
901 self.class_name, fully_qualified_class)
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900902
bulach@chromium.org6079a072012-02-24 09:09:38 +0900903
904 def GetContent(self):
905 """Returns the content of the JNI binding file."""
906 template = Template("""\
mkosiba@chromium.org33075172014-08-22 01:37:32 +0900907// Copyright 2014 The Chromium Authors. All rights reserved.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900908// Use of this source code is governed by a BSD-style license that can be
909// found in the LICENSE file.
910
911
912// This file is autogenerated by
913// ${SCRIPT_NAME}
914// For
915// ${FULLY_QUALIFIED_CLASS}
916
917#ifndef ${HEADER_GUARD}
918#define ${HEADER_GUARD}
919
920#include <jni.h>
921
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900922${INCLUDES}
bulach@chromium.org6079a072012-02-24 09:09:38 +0900923
924// Step 1: forward declarations.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900925$CLASS_PATH_DEFINITIONS
torneb66773d2016-05-10 02:08:20 +0900926
bulach@chromium.org1c775752012-06-22 19:03:16 +0900927$OPEN_NAMESPACE
bulach@chromium.org6079a072012-02-24 09:09:38 +0900928
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900929$CONSTANT_FIELDS
930
bulach@chromium.org6079a072012-02-24 09:09:38 +0900931// Step 2: method stubs.
932$METHOD_STUBS
933
Yipeng Wang97484ae2017-07-01 00:27:54 +0900934$CLOSE_NAMESPACE
torneb66773d2016-05-10 02:08:20 +0900935
bulach@chromium.org6079a072012-02-24 09:09:38 +0900936#endif // ${HEADER_GUARD}
937""")
bulach@chromium.org6079a072012-02-24 09:09:38 +0900938 values = {
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900939 'SCRIPT_NAME': self.options.script_name,
bulach@chromium.org6079a072012-02-24 09:09:38 +0900940 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
941 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900942 'CONSTANT_FIELDS': self.GetConstantFieldsString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900943 'METHOD_STUBS': self.GetMethodStubsString(),
944 'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
Yipeng Wang97484ae2017-07-01 00:27:54 +0900945 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900946 'HEADER_GUARD': self.header_guard,
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900947 'INCLUDES': self.GetIncludesString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900948 }
949 return WrapOutput(template.substitute(values))
950
951 def GetClassPathDefinitionsString(self):
Andrew Grievebe861e02017-08-15 05:08:33 +0900952 classes = self.helper.GetUniqueClasses(self.called_by_natives)
953 classes.update(self.helper.GetUniqueClasses(self.natives))
954 return self.helper.GetClassPathLines(classes)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900955
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900956 def GetConstantFieldsString(self):
957 if not self.constant_fields:
958 return ''
959 ret = ['enum Java_%s_constant_fields {' % self.class_name]
960 for c in self.constant_fields:
961 ret += [' %s = %s,' % (c.name, c.value)]
962 ret += ['};']
963 return '\n'.join(ret)
964
bulach@chromium.org6079a072012-02-24 09:09:38 +0900965 def GetMethodStubsString(self):
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900966 """Returns the code corresponding to method stubs."""
bulach@chromium.org6079a072012-02-24 09:09:38 +0900967 ret = []
968 for native in self.natives:
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +0900969 ret += [self.GetNativeStub(native)]
torneb66773d2016-05-10 02:08:20 +0900970 ret += self.GetLazyCalledByNativeMethodStubs()
bulach@chromium.org6079a072012-02-24 09:09:38 +0900971 return '\n'.join(ret)
972
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900973 def GetLazyCalledByNativeMethodStubs(self):
974 return [self.GetLazyCalledByNativeMethodStub(called_by_native)
975 for called_by_native in self.called_by_natives]
976
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900977 def GetIncludesString(self):
978 if not self.options.includes:
979 return ''
980 includes = self.options.includes.split(',')
981 return '\n'.join('#include "%s"' % x for x in includes)
982
bulach@chromium.org6079a072012-02-24 09:09:38 +0900983 def GetOpenNamespaceString(self):
984 if self.namespace:
bulach@chromium.org16dde202012-07-24 00:31:35 +0900985 all_namespaces = ['namespace %s {' % ns
986 for ns in self.namespace.split('::')]
987 return '\n'.join(all_namespaces)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900988 return ''
989
990 def GetCloseNamespaceString(self):
991 if self.namespace:
bulach@chromium.org16dde202012-07-24 00:31:35 +0900992 all_namespaces = ['} // namespace %s' % ns
993 for ns in self.namespace.split('::')]
994 all_namespaces.reverse()
995 return '\n'.join(all_namespaces) + '\n'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900996 return ''
997
bulach@chromium.org6079a072012-02-24 09:09:38 +0900998 def GetCalledByNativeParamsInDeclaration(self, called_by_native):
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900999 return ',\n '.join([
1000 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
1001 param.name
1002 for param in called_by_native.params])
bulach@chromium.org6079a072012-02-24 09:09:38 +09001003
tornec510c592015-09-04 20:16:35 +09001004 def GetJavaParamRefForCall(self, c_type, name):
torne9854c722016-08-05 00:44:06 +09001005 return Template(
1006 'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({
tornec510c592015-09-04 20:16:35 +09001007 'TYPE': c_type,
1008 'NAME': name,
1009 })
1010
1011 def GetJNIFirstParamForCall(self, native):
Andrew Grievebe861e02017-08-15 05:08:33 +09001012 c_type = _GetJNIFirstParamType(native)
tornec510c592015-09-04 20:16:35 +09001013 return [self.GetJavaParamRefForCall(c_type, 'jcaller')]
1014
Daniel Bratella10abb12017-11-22 02:51:25 +09001015 def GetImplementationMethodName(self, native):
1016 class_name = self.class_name
1017 if native.java_class_name is not None:
1018 # Inner class
1019 class_name = native.java_class_name
1020 return "JNI_%s_%s" % (class_name, native.name)
1021
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001022 def GetNativeStub(self, native):
1023 is_method = native.type == 'method'
1024
1025 if is_method:
1026 params = native.params[1:]
torne94bda722015-02-20 21:37:52 +09001027 else:
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001028 params = native.params
torneb66773d2016-05-10 02:08:20 +09001029 params_in_call = ['env'] + self.GetJNIFirstParamForCall(native)
tornec510c592015-09-04 20:16:35 +09001030 for p in params:
1031 c_type = JavaDataTypeToC(p.datatype)
1032 if re.match(RE_SCOPED_JNI_TYPES, c_type):
1033 params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name))
1034 else:
1035 params_in_call.append(p.name)
1036 params_in_call = ', '.join(params_in_call)
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001037
torne41495262015-08-26 23:07:32 +09001038 return_type = return_declaration = JavaDataTypeToC(native.return_type)
1039 post_call = ''
tornec510c592015-09-04 20:16:35 +09001040 if re.match(RE_SCOPED_JNI_TYPES, return_type):
torne41495262015-08-26 23:07:32 +09001041 post_call = '.Release()'
torne9854c722016-08-05 00:44:06 +09001042 return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type +
1043 '>')
dskibab7cdc892016-10-26 08:39:11 +09001044 profiling_entered_native = ''
1045 if self.options.enable_profiling:
1046 profiling_entered_native = 'JNI_LINK_SAVED_FRAME_POINTER;'
Siddhartha220aa232018-01-23 07:48:16 +09001047 # Temporary annotations for crbug.com/801260. Will be removed once the
1048 # memory usage is understood.
1049 trace_native_execution_scoped = (
1050 'TRACE_NATIVE_EXECUTION_SCOPED("' + native.name + '");')
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001051 values = {
1052 'RETURN': return_type,
torne41495262015-08-26 23:07:32 +09001053 'RETURN_DECLARATION': return_declaration,
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001054 'NAME': native.name,
Daniel Bratella10abb12017-11-22 02:51:25 +09001055 'IMPL_METHOD_NAME': self.GetImplementationMethodName(native),
Andrew Grievebe861e02017-08-15 05:08:33 +09001056 'PARAMS': _GetParamsInDeclaration(native),
1057 'PARAMS_IN_STUB': GetParamsInStub(native),
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001058 'PARAMS_IN_CALL': params_in_call,
torne41495262015-08-26 23:07:32 +09001059 'POST_CALL': post_call,
Andrew Grievebe861e02017-08-15 05:08:33 +09001060 'STUB_NAME': self.helper.GetStubName(native),
dskibab7cdc892016-10-26 08:39:11 +09001061 'PROFILING_ENTERED_NATIVE': profiling_entered_native,
Siddhartha220aa232018-01-23 07:48:16 +09001062 'TRACE_NATIVE_EXECUTION_SCOPED': trace_native_execution_scoped,
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001063 }
1064
1065 if is_method:
1066 optional_error_return = JavaReturnValueToC(native.return_type)
1067 if optional_error_return:
1068 optional_error_return = ', ' + optional_error_return
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001069 values.update({
1070 'OPTIONAL_ERROR_RETURN': optional_error_return,
1071 'PARAM0_NAME': native.params[0].name,
1072 'P0_TYPE': native.p0_type,
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001073 })
1074 template = Template("""\
torne17bc9642016-12-02 02:11:54 +09001075JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
dskibab7cdc892016-10-26 08:39:11 +09001076 ${PROFILING_ENTERED_NATIVE}
Siddhartha220aa232018-01-23 07:48:16 +09001077 ${TRACE_NATIVE_EXECUTION_SCOPED}
bulach@chromium.org6079a072012-02-24 09:09:38 +09001078 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001079 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
1080 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
bulach@chromium.org6079a072012-02-24 09:09:38 +09001081}
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001082""")
1083 else:
1084 template = Template("""
Daniel Bratella10abb12017-11-22 02:51:25 +09001085static ${RETURN_DECLARATION} ${IMPL_METHOD_NAME}(JNIEnv* env, ${PARAMS});
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001086
torne17bc9642016-12-02 02:11:54 +09001087JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
dskibab7cdc892016-10-26 08:39:11 +09001088 ${PROFILING_ENTERED_NATIVE}
Siddhartha220aa232018-01-23 07:48:16 +09001089 ${TRACE_NATIVE_EXECUTION_SCOPED}
Daniel Bratella10abb12017-11-22 02:51:25 +09001090 return ${IMPL_METHOD_NAME}(${PARAMS_IN_CALL})${POST_CALL};
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001091}
1092""")
bulach@chromium.org6079a072012-02-24 09:09:38 +09001093
dskibab7cdc892016-10-26 08:39:11 +09001094 return RemoveIndentedEmptyLines(template.substitute(values))
bulach@chromium.org6079a072012-02-24 09:09:38 +09001095
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001096 def GetArgument(self, param):
torne28061e92016-08-09 01:29:18 +09001097 if param.datatype == 'int':
1098 return 'as_jint(' + param.name + ')'
1099 elif re.match(RE_SCOPED_JNI_TYPES, JavaDataTypeToC(param.datatype)):
1100 return param.name + '.obj()'
1101 else:
1102 return param.name
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001103
1104 def GetArgumentsInCall(self, params):
1105 """Return a string of arguments to call from native into Java"""
1106 return [self.GetArgument(p) for p in params]
1107
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001108 def GetCalledByNativeValues(self, called_by_native):
1109 """Fills in necessary values for the CalledByNative methods."""
Andrew Grievee4cfd242017-07-29 04:47:28 +09001110 java_class_only = called_by_native.java_class_name or self.class_name
1111 java_class = self.fully_qualified_class
1112 if called_by_native.java_class_name:
1113 java_class += '$' + called_by_native.java_class_name
1114
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001115 if called_by_native.static or called_by_native.is_constructor:
1116 first_param_in_declaration = ''
Andrew Grievee4cfd242017-07-29 04:47:28 +09001117 first_param_in_call = ('%s_clazz(env)' % GetBinaryClassName(java_class))
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001118 else:
torne28061e92016-08-09 01:29:18 +09001119 first_param_in_declaration = (
Torne (Richard Coles)819c0562017-08-16 05:04:57 +09001120 ', const base::android::JavaRef<jobject>& obj')
torne28061e92016-08-09 01:29:18 +09001121 first_param_in_call = 'obj.obj()'
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001122 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
1123 called_by_native)
1124 if params_in_declaration:
1125 params_in_declaration = ', ' + params_in_declaration
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001126 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001127 if params_in_call:
1128 params_in_call = ', ' + params_in_call
1129 pre_call = ''
1130 post_call = ''
1131 if called_by_native.static_cast:
1132 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
1133 post_call = ')'
1134 check_exception = ''
1135 if not called_by_native.unchecked:
1136 check_exception = 'jni_generator::CheckException(env);'
1137 return_type = JavaDataTypeToC(called_by_native.return_type)
1138 optional_error_return = JavaReturnValueToC(called_by_native.return_type)
1139 if optional_error_return:
1140 optional_error_return = ', ' + optional_error_return
1141 return_declaration = ''
1142 return_clause = ''
1143 if return_type != 'void':
1144 pre_call = ' ' + pre_call
1145 return_declaration = return_type + ' ret ='
tornec510c592015-09-04 20:16:35 +09001146 if re.match(RE_SCOPED_JNI_TYPES, return_type):
torne9854c722016-08-05 00:44:06 +09001147 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001148 return_clause = 'return ' + return_type + '(env, ret);'
1149 else:
1150 return_clause = 'return ret;'
dskibab7cdc892016-10-26 08:39:11 +09001151 profiling_leaving_native = ''
1152 if self.options.enable_profiling:
1153 profiling_leaving_native = 'JNI_SAVE_FRAME_POINTER;'
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001154 return {
Andrew Grievee4cfd242017-07-29 04:47:28 +09001155 'JAVA_CLASS_ONLY': java_class_only,
1156 'JAVA_CLASS': GetBinaryClassName(java_class),
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001157 'RETURN_TYPE': return_type,
1158 'OPTIONAL_ERROR_RETURN': optional_error_return,
1159 'RETURN_DECLARATION': return_declaration,
1160 'RETURN_CLAUSE': return_clause,
1161 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
1162 'PARAMS_IN_DECLARATION': params_in_declaration,
1163 'PRE_CALL': pre_call,
1164 'POST_CALL': post_call,
1165 'ENV_CALL': called_by_native.env_call,
1166 'FIRST_PARAM_IN_CALL': first_param_in_call,
1167 'PARAMS_IN_CALL': params_in_call,
1168 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1169 'CHECK_EXCEPTION': check_exception,
dskibab7cdc892016-10-26 08:39:11 +09001170 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native),
1171 'PROFILING_LEAVING_NATIVE': profiling_leaving_native,
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001172 }
1173
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001174
1175 def GetLazyCalledByNativeMethodStub(self, called_by_native):
bulach@chromium.org6079a072012-02-24 09:09:38 +09001176 """Returns a string."""
1177 function_signature_template = Template("""\
Andrew Grievee4cfd242017-07-29 04:47:28 +09001178static ${RETURN_TYPE} Java_${JAVA_CLASS_ONLY}_${METHOD_ID_VAR_NAME}(\
bulach@chromium.org6079a072012-02-24 09:09:38 +09001179JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
1180 function_header_template = Template("""\
1181${FUNCTION_SIGNATURE} {""")
1182 function_header_with_unused_template = Template("""\
1183${FUNCTION_SIGNATURE} __attribute__ ((unused));
1184${FUNCTION_SIGNATURE} {""")
1185 template = Template("""
bulach@chromium.org231cff62012-10-17 03:35:10 +09001186static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
bulach@chromium.org6079a072012-02-24 09:09:38 +09001187${FUNCTION_HEADER}
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001188 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001189 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN});
bulach@chromium.org231cff62012-10-17 03:35:10 +09001190 jmethodID method_id =
1191 ${GET_METHOD_ID_IMPL}
dskibab7cdc892016-10-26 08:39:11 +09001192 ${PROFILING_LEAVING_NATIVE}
bulach@chromium.org6079a072012-02-24 09:09:38 +09001193 ${RETURN_DECLARATION}
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001194 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1195 method_id${PARAMS_IN_CALL})${POST_CALL};
bulach@chromium.org6079a072012-02-24 09:09:38 +09001196 ${CHECK_EXCEPTION}
1197 ${RETURN_CLAUSE}
1198}""")
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001199 values = self.GetCalledByNativeValues(called_by_native)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001200 values['FUNCTION_SIGNATURE'] = (
1201 function_signature_template.substitute(values))
1202 if called_by_native.system_class:
1203 values['FUNCTION_HEADER'] = (
1204 function_header_with_unused_template.substitute(values))
1205 else:
1206 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
dskibab7cdc892016-10-26 08:39:11 +09001207 return RemoveIndentedEmptyLines(template.substitute(values))
bulach@chromium.org6079a072012-02-24 09:09:38 +09001208
bulach@chromium.org6079a072012-02-24 09:09:38 +09001209 def GetMethodIDImpl(self, called_by_native):
1210 """Returns the implementation of GetMethodID."""
torneb66773d2016-05-10 02:08:20 +09001211 template = Template("""\
bulach@chromium.org231cff62012-10-17 03:35:10 +09001212 base::android::MethodID::LazyGet<
1213 base::android::MethodID::TYPE_${STATIC}>(
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001214 env, ${JAVA_CLASS}_clazz(env),
bulach@chromium.org231cff62012-10-17 03:35:10 +09001215 "${JNI_NAME}",
1216 ${JNI_SIGNATURE},
1217 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
bulach@chromium.org6079a072012-02-24 09:09:38 +09001218""")
bulach@chromium.org31af7532012-09-24 20:01:41 +09001219 jni_name = called_by_native.name
1220 jni_return_type = called_by_native.return_type
1221 if called_by_native.is_constructor:
1222 jni_name = '<init>'
1223 jni_return_type = 'void'
bulach@chromium.org37fc9112013-10-26 01:27:03 +09001224 if called_by_native.signature:
1225 signature = called_by_native.signature
1226 else:
Yipeng Wang60438ac2017-06-17 06:08:33 +09001227 signature = self.jni_params.Signature(called_by_native.params,
1228 jni_return_type, True)
Andrew Grievee4cfd242017-07-29 04:47:28 +09001229 java_class = self.fully_qualified_class
1230 if called_by_native.java_class_name:
1231 java_class += '$' + called_by_native.java_class_name
bulach@chromium.org6079a072012-02-24 09:09:38 +09001232 values = {
Andrew Grievee4cfd242017-07-29 04:47:28 +09001233 'JAVA_CLASS': GetBinaryClassName(java_class),
bulach@chromium.org31af7532012-09-24 20:01:41 +09001234 'JNI_NAME': jni_name,
bulach@chromium.org6079a072012-02-24 09:09:38 +09001235 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
bulach@chromium.org231cff62012-10-17 03:35:10 +09001236 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
bulach@chromium.org37fc9112013-10-26 01:27:03 +09001237 'JNI_SIGNATURE': signature,
bulach@chromium.org6079a072012-02-24 09:09:38 +09001238 }
1239 return template.substitute(values)
1240
1241
1242def WrapOutput(output):
1243 ret = []
1244 for line in output.splitlines():
tedchoc@chromium.org72992182012-08-03 11:00:17 +09001245 # Do not wrap lines under 80 characters or preprocessor directives.
1246 if len(line) < 80 or line.lstrip()[:1] == '#':
bulach@chromium.org1c775752012-06-22 19:03:16 +09001247 stripped = line.rstrip()
1248 if len(ret) == 0 or len(ret[-1]) or len(stripped):
1249 ret.append(stripped)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001250 else:
1251 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
1252 subsequent_indent = first_line_indent + ' ' * 4
1253 if line.startswith('//'):
1254 subsequent_indent = '//' + subsequent_indent
1255 wrapper = textwrap.TextWrapper(width=80,
1256 subsequent_indent=subsequent_indent,
1257 break_long_words=False)
1258 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
1259 ret += ['']
1260 return '\n'.join(ret)
1261
1262
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001263def ExtractJarInputFile(jar_file, input_file, out_dir):
1264 """Extracts input file from jar and returns the filename.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001265
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001266 The input file is extracted to the same directory that the generated jni
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001267 headers will be placed in. This is passed as an argument to script.
1268
1269 Args:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001270 jar_file: the jar file containing the input files to extract.
1271 input_files: the list of files to extract from the jar file.
1272 out_dir: the name of the directories to extract to.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001273
1274 Returns:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001275 the name of extracted input file.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001276 """
1277 jar_file = zipfile.ZipFile(jar_file)
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001278
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001279 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
torne@chromium.org108c37d2013-03-08 22:52:50 +09001280 try:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001281 os.makedirs(out_dir)
torne@chromium.org108c37d2013-03-08 22:52:50 +09001282 except OSError as e:
1283 if e.errno != errno.EEXIST:
1284 raise
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001285 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
1286 with open(extracted_file_name, 'w') as outfile:
1287 outfile.write(jar_file.read(input_file))
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001288
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001289 return extracted_file_name
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001290
1291
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001292def GenerateJNIHeader(input_file, output_file, options):
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001293 try:
1294 if os.path.splitext(input_file)[1] == '.class':
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001295 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001296 content = jni_from_javap.GetContent()
bulach@chromium.org6079a072012-02-24 09:09:38 +09001297 else:
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001298 jni_from_java_source = JNIFromJavaSource.CreateFromFile(
1299 input_file, options)
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001300 content = jni_from_java_source.GetContent()
1301 except ParseError, e:
1302 print e
1303 sys.exit(1)
1304 if output_file:
Yipeng Wangfba87a32017-07-01 03:16:41 +09001305 WriteOutput(output_file, content)
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001306 else:
paulmiller4d5b53e2015-05-30 02:24:37 +09001307 print content
bulach@chromium.org6079a072012-02-24 09:09:38 +09001308
1309
Yipeng Wangfba87a32017-07-01 03:16:41 +09001310def WriteOutput(output_file, content):
1311 if os.path.exists(output_file):
1312 with open(output_file) as f:
1313 existing_content = f.read()
1314 if existing_content == content:
1315 return
1316 with open(output_file, 'w') as f:
1317 f.write(content)
1318
1319
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001320def GetScriptName():
1321 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
1322 base_index = 0
1323 for idx, value in enumerate(script_components):
1324 if value == 'base' or value == 'third_party':
1325 base_index = idx
1326 break
1327 return os.sep.join(script_components[base_index:])
1328
1329
bulach@chromium.org6079a072012-02-24 09:09:38 +09001330def main(argv):
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001331 usage = """usage: %prog [OPTIONS]
bulach@chromium.org6079a072012-02-24 09:09:38 +09001332This script will parse the given java source code extracting the native
1333declarations and print the header file to stdout (or a file).
1334See SampleForTests.java for more details.
1335 """
1336 option_parser = optparse.OptionParser(usage=usage)
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +09001337 build_utils.AddDepfileOption(option_parser)
1338
cjhopman@chromium.orge607d252014-06-06 04:47:30 +09001339 option_parser.add_option('-j', '--jar_file', dest='jar_file',
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001340 help='Extract the list of input files from'
bulach@chromium.org7f85d462012-05-30 18:56:02 +09001341 ' a specified jar file.'
1342 ' Uses javap to extract the methods from a'
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001343 ' pre-compiled class. --input should point'
bulach@chromium.org6079a072012-02-24 09:09:38 +09001344 ' to pre-compiled Java .class files.')
1345 option_parser.add_option('-n', dest='namespace',
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001346 help='Uses as a namespace in the generated header '
1347 'instead of the javap class name, or when there is '
1348 'no JNINamespace annotation in the java source.')
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001349 option_parser.add_option('--input_file',
1350 help='Single input file name. The output file name '
1351 'will be derived from it. Must be used with '
1352 '--output_dir.')
1353 option_parser.add_option('--output_dir',
1354 help='The output directory. Must be used with '
1355 '--input')
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001356 option_parser.add_option('--script_name', default=GetScriptName(),
1357 help='The name of this script in the generated '
1358 'header.')
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001359 option_parser.add_option('--includes',
1360 help='The comma-separated list of header files to '
1361 'include in the generated header.')
bulach@chromium.orga022cf62013-11-05 09:54:22 +09001362 option_parser.add_option('--ptr_type', default='int',
1363 type='choice', choices=['int', 'long'],
1364 help='The type used to represent native pointers in '
1365 'Java code. For 32-bit, use int; '
1366 'for 64-bit, use long.')
bulach@chromium.org92d0dac2014-01-17 01:08:05 +09001367 option_parser.add_option('--cpp', default='cpp',
1368 help='The path to cpp command.')
1369 option_parser.add_option('--javap', default='javap',
1370 help='The path to javap command.')
dskibab7cdc892016-10-26 08:39:11 +09001371 option_parser.add_option('--enable_profiling', action='store_true',
1372 help='Add additional profiling instrumentation.')
bulach@chromium.org6079a072012-02-24 09:09:38 +09001373 options, args = option_parser.parse_args(argv)
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001374 if options.jar_file:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001375 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1376 options.output_dir)
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001377 elif options.input_file:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001378 input_file = options.input_file
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001379 else:
1380 option_parser.print_help()
1381 print '\nError: Must specify --jar_file or --input_file.'
1382 return 1
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001383 output_file = None
1384 if options.output_dir:
1385 root_name = os.path.splitext(os.path.basename(input_file))[0]
1386 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001387 GenerateJNIHeader(input_file, output_file, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001388
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +09001389 if options.depfile:
agrieve92aed192016-09-14 11:04:21 +09001390 build_utils.WriteDepfile(options.depfile, output_file)
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +09001391
bulach@chromium.org6079a072012-02-24 09:09:38 +09001392
1393if __name__ == '__main__':
1394 sys.exit(main(sys.argv))