blob: 3818009ade4fa44038db2c3d9781025b0b378a46 [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',
tornec9124232015-08-27 23:57:06 +0900116 'Throwable': 'jthrowable',
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900117 'java/lang/String': 'jstring',
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900118 'java/lang/Class': 'jclass',
tornec9124232015-08-27 23:57:06 +0900119 'java/lang/Throwable': 'jthrowable',
bulach@chromium.org6079a072012-02-24 09:09:38 +0900120 }
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900121
bulach@chromium.org6079a072012-02-24 09:09:38 +0900122 if java_type in java_pod_type_map:
123 return java_pod_type_map[java_type]
124 elif java_type in java_type_map:
125 return java_type_map[java_type]
126 elif java_type.endswith('[]'):
127 if java_type[:-2] in java_pod_type_map:
128 return java_pod_type_map[java_type[:-2]] + 'Array'
129 return 'jobjectArray'
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900130 elif java_type.startswith('Class'):
131 # Checking just the start of the name, rather than a direct comparison,
132 # in order to handle generics.
133 return 'jclass'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900134 else:
135 return 'jobject'
136
137
tornec510c592015-09-04 20:16:35 +0900138def WrapCTypeForDeclaration(c_type):
139 """Wrap the C datatype in a JavaRef if required."""
140 if re.match(RE_SCOPED_JNI_TYPES, c_type):
torne9854c722016-08-05 00:44:06 +0900141 return 'const base::android::JavaParamRef<' + c_type + '>&'
tornec510c592015-09-04 20:16:35 +0900142 else:
143 return c_type
144
145
146def JavaDataTypeToCForDeclaration(java_type):
147 """Returns a JavaRef-wrapped C datatype for the given java type."""
148 return WrapCTypeForDeclaration(JavaDataTypeToC(java_type))
149
150
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900151def JavaDataTypeToCForCalledByNativeParam(java_type):
152 """Returns a C datatype to be when calling from native."""
153 if java_type == 'int':
154 return 'JniIntWrapper'
155 else:
torne28061e92016-08-09 01:29:18 +0900156 c_type = JavaDataTypeToC(java_type)
157 if re.match(RE_SCOPED_JNI_TYPES, c_type):
158 return 'const base::android::JavaRefOrBare<' + c_type + '>&'
159 else:
160 return c_type
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900161
162
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900163def JavaReturnValueToC(java_type):
164 """Returns a valid C return value for the given java type."""
165 java_pod_type_map = {
166 'int': '0',
167 'byte': '0',
168 'char': '0',
169 'short': '0',
170 'boolean': 'false',
171 'long': '0',
172 'double': '0',
173 'float': '0',
174 'void': ''
175 }
176 return java_pod_type_map.get(java_type, 'NULL')
177
178
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900179class JniParams(object):
Yipeng Wang60438ac2017-06-17 06:08:33 +0900180 """Get JNI related parameters."""
bulach@chromium.org6079a072012-02-24 09:09:38 +0900181
Yipeng Wang60438ac2017-06-17 06:08:33 +0900182 def __init__(self, fully_qualified_class):
183 self._fully_qualified_class = 'L' + fully_qualified_class
184 self._package = '/'.join(fully_qualified_class.split('/')[:-1])
185 self._imports = []
186 self._inner_classes = []
187 self._implicit_imports = []
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900188
Yipeng Wang60438ac2017-06-17 06:08:33 +0900189 def ExtractImportsAndInnerClasses(self, contents):
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900190 contents = contents.replace('\n', '')
191 re_import = re.compile(r'import.*?(?P<class>\S*?);')
192 for match in re.finditer(re_import, contents):
Yipeng Wang60438ac2017-06-17 06:08:33 +0900193 self._imports += ['L' + match.group('class').replace('.', '/')]
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900194
195 re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W')
196 for match in re.finditer(re_inner, contents):
197 inner = match.group('name')
Yipeng Wang60438ac2017-06-17 06:08:33 +0900198 if not self._fully_qualified_class.endswith(inner):
199 self._inner_classes += [self._fully_qualified_class + '$' +
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900200 inner]
bulach@chromium.org6079a072012-02-24 09:09:38 +0900201
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900202 re_additional_imports = re.compile(
qsr@chromium.org74f541e2014-05-23 23:44:37 +0900203 r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900204 for match in re.finditer(re_additional_imports, contents):
qsr@chromium.org74f541e2014-05-23 23:44:37 +0900205 for class_name in match.group('class_names').split(','):
Yipeng Wang60438ac2017-06-17 06:08:33 +0900206 self._AddAdditionalImport(class_name.strip())
qsr@chromium.org47bd3462014-05-19 23:26:49 +0900207
Yipeng Wang60438ac2017-06-17 06:08:33 +0900208 def JavaToJni(self, param):
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900209 """Converts a java param into a JNI signature type."""
210 pod_param_map = {
211 'int': 'I',
212 'boolean': 'Z',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900213 'char': 'C',
214 'short': 'S',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900215 'long': 'J',
216 'double': 'D',
217 'float': 'F',
218 'byte': 'B',
219 'void': 'V',
220 }
221 object_param_list = [
222 'Ljava/lang/Boolean',
223 'Ljava/lang/Integer',
224 'Ljava/lang/Long',
225 'Ljava/lang/Object',
226 'Ljava/lang/String',
dtrainor@chromium.orgf7e99772012-12-06 08:27:41 +0900227 'Ljava/lang/Class',
aurimas9e29f732015-06-25 07:53:10 +0900228 'Ljava/lang/CharSequence',
229 'Ljava/lang/Runnable',
230 'Ljava/lang/Throwable',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900231 ]
bulach@chromium.org69931302014-05-08 04:16:23 +0900232
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900233 prefix = ''
234 # Array?
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900235 while param[-2:] == '[]':
236 prefix += '['
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900237 param = param[:-2]
238 # Generic?
239 if '<' in param:
240 param = param[:param.index('<')]
241 if param in pod_param_map:
242 return prefix + pod_param_map[param]
243 if '/' in param:
244 # Coming from javap, use the fully qualified param directly.
torneb66773d2016-05-10 02:08:20 +0900245 return prefix + 'L' + param + ';'
bulach@chromium.org4a130cc2014-03-28 20:15:06 +0900246
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900247 for qualified_name in (object_param_list +
Yipeng Wang60438ac2017-06-17 06:08:33 +0900248 [self._fully_qualified_class] + self._inner_classes):
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900249 if (qualified_name.endswith('/' + param) or
250 qualified_name.endswith('$' + param.replace('.', '$')) or
251 qualified_name == 'L' + param):
torneb66773d2016-05-10 02:08:20 +0900252 return prefix + qualified_name + ';'
bulach@chromium.org7156c762012-11-13 23:35:00 +0900253
254 # Is it from an import? (e.g. referecing Class from import pkg.Class;
255 # note that referencing an inner class Inner from import pkg.Class.Inner
256 # is not supported).
Yipeng Wang60438ac2017-06-17 06:08:33 +0900257 for qualified_name in self._imports:
bulach@chromium.org7156c762012-11-13 23:35:00 +0900258 if qualified_name.endswith('/' + param):
259 # Ensure it's not an inner class.
260 components = qualified_name.split('/')
261 if len(components) > 2 and components[-2][0].isupper():
262 raise SyntaxError('Inner class (%s) can not be imported '
263 'and used by JNI (%s). Please import the outer '
264 'class and use Outer.Inner instead.' %
265 (qualified_name, param))
torneb66773d2016-05-10 02:08:20 +0900266 return prefix + qualified_name + ';'
bulach@chromium.org7156c762012-11-13 23:35:00 +0900267
268 # Is it an inner class from an outer class import? (e.g. referencing
269 # Class.Inner from import pkg.Class).
270 if '.' in param:
271 components = param.split('.')
272 outer = '/'.join(components[:-1])
273 inner = components[-1]
Yipeng Wang60438ac2017-06-17 06:08:33 +0900274 for qualified_name in self._imports:
bulach@chromium.org7156c762012-11-13 23:35:00 +0900275 if qualified_name.endswith('/' + outer):
torneb66773d2016-05-10 02:08:20 +0900276 return (prefix + qualified_name + '$' + inner + ';')
bulach@chromium.org4a130cc2014-03-28 20:15:06 +0900277 raise SyntaxError('Inner class (%s) can not be '
278 'used directly by JNI. Please import the outer '
279 'class, probably:\n'
280 'import %s.%s;' %
Yipeng Wang60438ac2017-06-17 06:08:33 +0900281 (param, self._package.replace('/', '.'),
bulach@chromium.org4a130cc2014-03-28 20:15:06 +0900282 outer.replace('/', '.')))
bulach@chromium.org7156c762012-11-13 23:35:00 +0900283
Yipeng Wang60438ac2017-06-17 06:08:33 +0900284 self._CheckImplicitImports(param)
bulach@chromium.org69931302014-05-08 04:16:23 +0900285
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900286 # Type not found, falling back to same package as this class.
Yipeng Wang60438ac2017-06-17 06:08:33 +0900287 return (prefix + 'L' + self._package + '/' + param + ';')
bulach@chromium.org6079a072012-02-24 09:09:38 +0900288
Yipeng Wang60438ac2017-06-17 06:08:33 +0900289 def _AddAdditionalImport(self, class_name):
290 assert class_name.endswith('.class')
291 raw_class_name = class_name[:-len('.class')]
292 if '.' in raw_class_name:
293 raise SyntaxError('%s cannot be used in @JNIAdditionalImport. '
294 'Only import unqualified outer classes.' % class_name)
295 new_import = 'L%s/%s' % (self._package, raw_class_name)
296 if new_import in self._imports:
297 raise SyntaxError('Do not use JNIAdditionalImport on an already '
298 'imported class: %s' % (new_import.replace('/', '.')))
299 self._imports += [new_import]
300
301 def _CheckImplicitImports(self, param):
bulach@chromium.org69931302014-05-08 04:16:23 +0900302 # Ensure implicit imports, such as java.lang.*, are not being treated
303 # as being in the same package.
Yipeng Wang60438ac2017-06-17 06:08:33 +0900304 if not self._implicit_imports:
bulach@chromium.org69931302014-05-08 04:16:23 +0900305 # 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:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900309 self._implicit_imports = f.readlines()
310 for implicit_import in self._implicit_imports:
bulach@chromium.org69931302014-05-08 04:16:23 +0900311 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
Yipeng Wang60438ac2017-06-17 06:08:33 +0900319 def Signature(self, params, returns, wrap):
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900320 """Returns the JNI signature for the given datatypes."""
321 items = ['(']
Yipeng Wang60438ac2017-06-17 06:08:33 +0900322 items += [self.JavaToJni(param.datatype) for param in params]
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900323 items += [')']
Yipeng Wang60438ac2017-06-17 06:08:33 +0900324 items += [self.JavaToJni(returns)]
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900325 if wrap:
326 return '\n' + '\n'.join(['"' + item + '"' for item in items])
327 else:
328 return '"' + ''.join(items) + '"'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900329
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900330 @staticmethod
Yipeng Wang60438ac2017-06-17 06:08:33 +0900331 def ParseJavaPSignature(signature_line):
332 prefix = 'Signature: '
333 index = signature_line.find(prefix)
334 if index == -1:
335 prefix = 'descriptor: '
336 index = signature_line.index(prefix)
337 return '"%s"' % signature_line[index + len(prefix):]
338
339 @staticmethod
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900340 def Parse(params):
341 """Parses the params into a list of Param objects."""
342 if not params:
343 return []
344 ret = []
345 for p in [p.strip() for p in params.split(',')]:
346 items = p.split(' ')
bauerbb7c53862016-08-25 22:41:41 +0900347
348 # Remove @Annotations from parameters.
349 while items[0].startswith('@'):
350 del items[0]
351
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900352 if 'final' in items:
353 items.remove('final')
bauerbb7c53862016-08-25 22:41:41 +0900354
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900355 param = Param(
356 datatype=items[0],
357 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
358 )
359 ret += [param]
360 return ret
bulach@chromium.org6079a072012-02-24 09:09:38 +0900361
bulach@chromium.org6079a072012-02-24 09:09:38 +0900362
bulach@chromium.org1c775752012-06-22 19:03:16 +0900363def ExtractJNINamespace(contents):
364 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
365 m = re.findall(re_jni_namespace, contents)
366 if not m:
367 return ''
368 return m[0]
369
370
bulach@chromium.org6079a072012-02-24 09:09:38 +0900371def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
372 re_package = re.compile('.*?package (.*?);')
373 matches = re.findall(re_package, contents)
374 if not matches:
375 raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
376 return (matches[0].replace('.', '/') + '/' +
377 os.path.splitext(os.path.basename(java_file_name))[0])
378
379
bulach@chromium.orga022cf62013-11-05 09:54:22 +0900380def ExtractNatives(contents, ptr_type):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900381 """Returns a list of dict containing information about a native method."""
382 contents = contents.replace('\n', '')
383 natives = []
bulach@chromium.org1c775752012-06-22 19:03:16 +0900384 re_native = re.compile(r'(@NativeClassQualifiedName'
bulach@chromium.org86eae282014-05-10 21:15:07 +0900385 '\(\"(?P<native_class_name>.*?)\"\)\s+)?'
386 '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
387 '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
388 '(?P<return_type>\S*) '
389 '(?P<name>native\w+)\((?P<params>.*?)\);')
bulach@chromium.org1c775752012-06-22 19:03:16 +0900390 for match in re.finditer(re_native, contents):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900391 native = NativeMethod(
bulach@chromium.org1c775752012-06-22 19:03:16 +0900392 static='static' in match.group('qualifiers'),
393 java_class_name=match.group('java_class_name'),
394 native_class_name=match.group('native_class_name'),
bulach@chromium.orga6e185e2013-03-26 16:32:39 +0900395 return_type=match.group('return_type'),
bulach@chromium.org1c775752012-06-22 19:03:16 +0900396 name=match.group('name').replace('native', ''),
bulach@chromium.orga022cf62013-11-05 09:54:22 +0900397 params=JniParams.Parse(match.group('params')),
398 ptr_type=ptr_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900399 natives += [native]
400 return natives
401
402
estevenson2498e5d2017-02-01 05:34:56 +0900403def IsMainDexJavaClass(contents):
Yipeng Wang97484ae2017-07-01 00:27:54 +0900404 """Returns "true" if the class is annotated with "@MainDex", "false" if not.
estevenson2498e5d2017-02-01 05:34:56 +0900405
406 JNI registration doesn't always need to be completed for non-browser processes
407 since most Java code is only used by the browser process. Classes that are
408 needed by non-browser processes must explicitly be annotated with @MainDex
409 to force JNI registration.
410 """
Jay Civelli76455192017-06-10 06:57:19 +0900411 re_maindex = re.compile(r'@MainDex[\s\S]*class({|[\s\S]*{)')
Yipeng Wang97484ae2017-07-01 00:27:54 +0900412 found = re.search(re_maindex, contents)
413 return 'true' if found else 'false'
estevenson2498e5d2017-02-01 05:34:56 +0900414
415
bulach@chromium.org31af7532012-09-24 20:01:41 +0900416def GetStaticCastForReturnType(return_type):
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900417 type_map = { 'String' : 'jstring',
418 'java/lang/String' : 'jstring',
tornec9124232015-08-27 23:57:06 +0900419 'Throwable': 'jthrowable',
420 'java/lang/Throwable': 'jthrowable',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900421 'boolean[]': 'jbooleanArray',
422 'byte[]': 'jbyteArray',
423 'char[]': 'jcharArray',
424 'short[]': 'jshortArray',
425 'int[]': 'jintArray',
426 'long[]': 'jlongArray',
skhatri@nvidia.comf5bf8c42014-06-02 21:23:12 +0900427 'float[]': 'jfloatArray',
digit@chromium.org9d7eab02012-12-21 05:33:03 +0900428 'double[]': 'jdoubleArray' }
429 ret = type_map.get(return_type, None)
430 if ret:
431 return ret
432 if return_type.endswith('[]'):
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900433 return 'jobjectArray'
bulach@chromium.org31af7532012-09-24 20:01:41 +0900434 return None
435
436
437def GetEnvCall(is_constructor, is_static, return_type):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900438 """Maps the types availabe via env->Call__Method."""
bulach@chromium.org31af7532012-09-24 20:01:41 +0900439 if is_constructor:
440 return 'NewObject'
441 env_call_map = {'boolean': 'Boolean',
442 'byte': 'Byte',
443 'char': 'Char',
444 'short': 'Short',
445 'int': 'Int',
446 'long': 'Long',
447 'float': 'Float',
448 'void': 'Void',
449 'double': 'Double',
450 'Object': 'Object',
bulach@chromium.org6079a072012-02-24 09:09:38 +0900451 }
bulach@chromium.org31af7532012-09-24 20:01:41 +0900452 call = env_call_map.get(return_type, 'Object')
453 if is_static:
454 call = 'Static' + call
455 return 'Call' + call + 'Method'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900456
457
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900458def GetMangledParam(datatype):
459 """Returns a mangled identifier for the datatype."""
460 if len(datatype) <= 2:
461 return datatype.replace('[', 'A')
462 ret = ''
463 for i in range(1, len(datatype)):
464 c = datatype[i]
465 if c == '[':
466 ret += 'A'
467 elif c.isupper() or datatype[i - 1] in ['/', 'L']:
468 ret += c.upper()
469 return ret
470
471
Yipeng Wang60438ac2017-06-17 06:08:33 +0900472def GetMangledMethodName(jni_params, name, params, return_type):
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900473 """Returns a mangled method name for the given signature.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900474
475 The returned name can be used as a C identifier and will be unique for all
476 valid overloads of the same method.
477
478 Args:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900479 jni_params: JniParams object.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900480 name: string.
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900481 params: list of Param.
482 return_type: string.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900483
484 Returns:
485 A mangled name.
486 """
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900487 mangled_items = []
488 for datatype in [return_type] + [x.datatype for x in params]:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900489 mangled_items += [GetMangledParam(jni_params.JavaToJni(datatype))]
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900490 mangled_name = name + '_'.join(mangled_items)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900491 assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
492 return mangled_name
493
494
Yipeng Wang60438ac2017-06-17 06:08:33 +0900495def MangleCalledByNatives(jni_params, called_by_natives):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900496 """Mangles all the overloads from the call_by_natives list."""
497 method_counts = collections.defaultdict(
498 lambda: collections.defaultdict(lambda: 0))
499 for called_by_native in called_by_natives:
500 java_class_name = called_by_native.java_class_name
501 name = called_by_native.name
502 method_counts[java_class_name][name] += 1
503 for called_by_native in called_by_natives:
504 java_class_name = called_by_native.java_class_name
505 method_name = called_by_native.name
506 method_id_var_name = method_name
507 if method_counts[java_class_name][method_name] > 1:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900508 method_id_var_name = GetMangledMethodName(jni_params, method_name,
bulach@chromium.org0c6805b2012-09-28 21:34:33 +0900509 called_by_native.params,
510 called_by_native.return_type)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900511 called_by_native.method_id_var_name = method_id_var_name
512 return called_by_natives
513
514
tornec510c592015-09-04 20:16:35 +0900515# Regex to match the JNI types that should be wrapped in a JavaRef.
516RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array')
517
bulach@chromium.org6079a072012-02-24 09:09:38 +0900518
519# Regex to match a string like "@CalledByNative public void foo(int bar)".
520RE_CALLED_BY_NATIVE = re.compile(
521 '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
522 '\s+(?P<prefix>[\w ]*?)'
estevenson0621dbe2016-10-01 05:52:49 +0900523 '(:?\s*@\w+)?' # Ignore annotations in return types.
bulach@chromium.orga6e185e2013-03-26 16:32:39 +0900524 '\s*(?P<return_type>\S+?)'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900525 '\s+(?P<name>\w+)'
526 '\s*\((?P<params>[^\)]*)\)')
527
528
dskibab7cdc892016-10-26 08:39:11 +0900529# Removes empty lines that are indented (i.e. start with 2x spaces).
530def RemoveIndentedEmptyLines(string):
531 return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE)
532
533
Yipeng Wang60438ac2017-06-17 06:08:33 +0900534def ExtractCalledByNatives(jni_params, contents):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900535 """Parses all methods annotated with @CalledByNative.
536
537 Args:
Yipeng Wang60438ac2017-06-17 06:08:33 +0900538 jni_params: JniParams object.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900539 contents: the contents of the java file.
540
541 Returns:
542 A list of dict with information about the annotated methods.
543 TODO(bulach): return a CalledByNative object.
544
545 Raises:
546 ParseError: if unable to parse.
547 """
548 called_by_natives = []
549 for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
550 called_by_natives += [CalledByNative(
551 system_class=False,
552 unchecked='Unchecked' in match.group('Unchecked'),
553 static='static' in match.group('prefix'),
554 java_class_name=match.group('annotation') or '',
555 return_type=match.group('return_type'),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900556 name=match.group('name'),
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900557 params=JniParams.Parse(match.group('params')))]
bulach@chromium.org6079a072012-02-24 09:09:38 +0900558 # Check for any @CalledByNative occurrences that weren't matched.
559 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
560 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
561 if '@CalledByNative' in line1:
562 raise ParseError('could not parse @CalledByNative method signature',
563 line1, line2)
Yipeng Wang60438ac2017-06-17 06:08:33 +0900564 return MangleCalledByNatives(jni_params, called_by_natives)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900565
566
567class JNIFromJavaP(object):
568 """Uses 'javap' to parse a .class file and generate the JNI header file."""
569
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900570 def __init__(self, contents, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900571 self.contents = contents
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900572 self.namespace = options.namespace
bulach@chromium.org07a9ba42014-02-28 03:23:11 +0900573 for line in contents:
574 class_name = re.match(
575 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
576 line)
577 if class_name:
578 self.fully_qualified_class = class_name.group('class_name')
579 break
bulach@chromium.org6079a072012-02-24 09:09:38 +0900580 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
simonb@opera.com521714e2013-09-04 06:22:48 +0900581 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip
582 # away the <...> and use the raw class name that Java 6 would've given us.
583 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
Yipeng Wang60438ac2017-06-17 06:08:33 +0900584 self.jni_params = JniParams(self.fully_qualified_class)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900585 self.java_class_name = self.fully_qualified_class.split('/')[-1]
586 if not self.namespace:
587 self.namespace = 'JNI_' + self.java_class_name
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900588 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
bulach@chromium.org31af7532012-09-24 20:01:41 +0900589 '\((?P<params>.*?)\)')
bulach@chromium.org6079a072012-02-24 09:09:38 +0900590 self.called_by_natives = []
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900591 for lineno, content in enumerate(contents[2:], 2):
bulach@chromium.org31af7532012-09-24 20:01:41 +0900592 match = re.match(re_method, content)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900593 if not match:
594 continue
595 self.called_by_natives += [CalledByNative(
596 system_class=True,
597 unchecked=False,
bulach@chromium.org31af7532012-09-24 20:01:41 +0900598 static='static' in match.group('prefix'),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900599 java_class_name='',
bulach@chromium.org0bafda72012-11-08 04:06:51 +0900600 return_type=match.group('return_type').replace('.', '/'),
bulach@chromium.org31af7532012-09-24 20:01:41 +0900601 name=match.group('name'),
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900602 params=JniParams.Parse(match.group('params').replace('.', '/')),
603 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
604 re_constructor = re.compile('(.*?)public ' +
bulach@chromium.org31af7532012-09-24 20:01:41 +0900605 self.fully_qualified_class.replace('/', '.') +
606 '\((?P<params>.*?)\)')
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900607 for lineno, content in enumerate(contents[2:], 2):
bulach@chromium.org31af7532012-09-24 20:01:41 +0900608 match = re.match(re_constructor, content)
609 if not match:
610 continue
611 self.called_by_natives += [CalledByNative(
bulach@chromium.org208f05b2012-10-03 06:13:55 +0900612 system_class=True,
bulach@chromium.org31af7532012-09-24 20:01:41 +0900613 unchecked=False,
614 static=False,
615 java_class_name='',
616 return_type=self.fully_qualified_class,
617 name='Constructor',
bulach@chromium.orgc6b75842012-11-01 07:58:15 +0900618 params=JniParams.Parse(match.group('params').replace('.', '/')),
bulach@chromium.org37fc9112013-10-26 01:27:03 +0900619 signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
bulach@chromium.org31af7532012-09-24 20:01:41 +0900620 is_constructor=True)]
Yipeng Wang60438ac2017-06-17 06:08:33 +0900621 self.called_by_natives = MangleCalledByNatives(self.jni_params,
622 self.called_by_natives)
Yipeng Wang97484ae2017-07-01 00:27:54 +0900623
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900624 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(
estevenson2498e5d2017-02-01 05:34:56 +0900641 self.namespace, self.fully_qualified_class, [], self.called_by_natives,
Yipeng Wang60438ac2017-06-17 06:08:33 +0900642 self.constant_fields, self.jni_params, 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)
Yipeng Wang60438ac2017-06-17 06:08:33 +0900671 self.jni_params = JniParams(fully_qualified_class)
672 self.jni_params.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)
Yipeng Wang60438ac2017-06-17 06:08:33 +0900675 called_by_natives = ExtractCalledByNatives(self.jni_params, contents)
Yipeng Wang97484ae2017-07-01 00:27:54 +0900676 maindex = IsMainDexJavaClass(contents)
bulach@chromium.orge2530d02012-07-03 01:23:35 +0900677 if len(natives) == 0 and len(called_by_natives) == 0:
678 raise SyntaxError('Unable to find any JNI methods for %s.' %
679 fully_qualified_class)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900680 inl_header_file_generator = InlHeaderFileGenerator(
estevenson2498e5d2017-02-01 05:34:56 +0900681 jni_namespace, fully_qualified_class, natives, called_by_natives, [],
Yipeng Wang97484ae2017-07-01 00:27:54 +0900682 self.jni_params, options, maindex)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900683 self.content = inl_header_file_generator.GetContent()
684
torne@chromium.org202d2672014-04-15 02:29:59 +0900685 @classmethod
686 def _RemoveComments(cls, contents):
husky@chromium.org8c438d72012-07-31 02:27:48 +0900687 # We need to support both inline and block comments, and we need to handle
torne@chromium.org202d2672014-04-15 02:29:59 +0900688 # strings that contain '//' or '/*'.
689 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
husky@chromium.org8c438d72012-07-31 02:27:48 +0900690 # parser. Maybe we could ditch JNIFromJavaSource and just always use
691 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
692 # http://code.google.com/p/chromium/issues/detail?id=138941
torne@chromium.org202d2672014-04-15 02:29:59 +0900693 def replacer(match):
694 # Replace matches that are comments with nothing; return literals/strings
695 # unchanged.
696 s = match.group(0)
697 if s.startswith('/'):
698 return ''
699 else:
700 return s
701 return cls._comment_remover_regex.sub(replacer, contents)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900702
703 def GetContent(self):
704 return self.content
705
706 @staticmethod
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900707 def CreateFromFile(java_file_name, options):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900708 contents = file(java_file_name).read()
709 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
710 contents)
bulach@chromium.org7cd403c2013-10-03 03:11:24 +0900711 return JNIFromJavaSource(contents, fully_qualified_class, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900712
713
714class InlHeaderFileGenerator(object):
715 """Generates an inline header file for JNI integration."""
716
717 def __init__(self, namespace, fully_qualified_class, natives,
Yipeng Wang97484ae2017-07-01 00:27:54 +0900718 called_by_natives, constant_fields, jni_params, options,
719 maindex='false'):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900720 self.namespace = namespace
721 self.fully_qualified_class = fully_qualified_class
722 self.class_name = self.fully_qualified_class.split('/')[-1]
723 self.natives = natives
724 self.called_by_natives = called_by_natives
725 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900726 self.constant_fields = constant_fields
Yipeng Wang97484ae2017-07-01 00:27:54 +0900727 self.maindex = maindex
Yipeng Wang60438ac2017-06-17 06:08:33 +0900728 self.jni_params = jni_params
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900729 self.options = options
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900730
bulach@chromium.org6079a072012-02-24 09:09:38 +0900731
732 def GetContent(self):
733 """Returns the content of the JNI binding file."""
734 template = Template("""\
mkosiba@chromium.org33075172014-08-22 01:37:32 +0900735// Copyright 2014 The Chromium Authors. All rights reserved.
bulach@chromium.org6079a072012-02-24 09:09:38 +0900736// Use of this source code is governed by a BSD-style license that can be
737// found in the LICENSE file.
738
739
740// This file is autogenerated by
741// ${SCRIPT_NAME}
742// For
743// ${FULLY_QUALIFIED_CLASS}
744
745#ifndef ${HEADER_GUARD}
746#define ${HEADER_GUARD}
747
748#include <jni.h>
749
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900750${INCLUDES}
bulach@chromium.org6079a072012-02-24 09:09:38 +0900751
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900752#include "base/android/jni_int_wrapper.h"
753
bulach@chromium.org6079a072012-02-24 09:09:38 +0900754// Step 1: forward declarations.
755namespace {
756$CLASS_PATH_DEFINITIONS
torneb66773d2016-05-10 02:08:20 +0900757
bulach@chromium.org6079a072012-02-24 09:09:38 +0900758} // namespace
bulach@chromium.org1c775752012-06-22 19:03:16 +0900759
760$OPEN_NAMESPACE
bulach@chromium.org6079a072012-02-24 09:09:38 +0900761
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900762$CONSTANT_FIELDS
763
bulach@chromium.org6079a072012-02-24 09:09:38 +0900764// Step 2: method stubs.
765$METHOD_STUBS
766
bulach@chromium.org231cff62012-10-17 03:35:10 +0900767// Step 3: RegisterNatives.
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900768$JNI_NATIVE_METHODS
Yipeng Wang441852a2017-06-30 23:10:39 +0900769$REGISTER_NATIVES
Yipeng Wang97484ae2017-07-01 00:27:54 +0900770$CLOSE_NAMESPACE
torneb66773d2016-05-10 02:08:20 +0900771
bulach@chromium.org6079a072012-02-24 09:09:38 +0900772#endif // ${HEADER_GUARD}
773""")
bulach@chromium.org6079a072012-02-24 09:09:38 +0900774 values = {
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900775 'SCRIPT_NAME': self.options.script_name,
bulach@chromium.org6079a072012-02-24 09:09:38 +0900776 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
777 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900778 'CONSTANT_FIELDS': self.GetConstantFieldsString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900779 'METHOD_STUBS': self.GetMethodStubsString(),
780 'OPEN_NAMESPACE': self.GetOpenNamespaceString(),
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900781 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(),
Yipeng Wang441852a2017-06-30 23:10:39 +0900782 'REGISTER_NATIVES': self.GetRegisterNativesString(),
Yipeng Wang97484ae2017-07-01 00:27:54 +0900783 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900784 'HEADER_GUARD': self.header_guard,
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900785 'INCLUDES': self.GetIncludesString(),
bulach@chromium.org6079a072012-02-24 09:09:38 +0900786 }
sievers2c26d4b2016-08-04 08:20:40 +0900787 assert ((values['JNI_NATIVE_METHODS'] == '') ==
788 (values['REGISTER_NATIVES'] == ''))
bulach@chromium.org6079a072012-02-24 09:09:38 +0900789 return WrapOutput(template.substitute(values))
790
791 def GetClassPathDefinitionsString(self):
792 ret = []
793 ret += [self.GetClassPathDefinitions()]
794 return '\n'.join(ret)
795
bulach@chromium.org658dbc02014-02-27 04:59:00 +0900796 def GetConstantFieldsString(self):
797 if not self.constant_fields:
798 return ''
799 ret = ['enum Java_%s_constant_fields {' % self.class_name]
800 for c in self.constant_fields:
801 ret += [' %s = %s,' % (c.name, c.value)]
802 ret += ['};']
803 return '\n'.join(ret)
804
bulach@chromium.org6079a072012-02-24 09:09:38 +0900805 def GetMethodStubsString(self):
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900806 """Returns the code corresponding to method stubs."""
bulach@chromium.org6079a072012-02-24 09:09:38 +0900807 ret = []
808 for native in self.natives:
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +0900809 ret += [self.GetNativeStub(native)]
torneb66773d2016-05-10 02:08:20 +0900810 ret += self.GetLazyCalledByNativeMethodStubs()
bulach@chromium.org6079a072012-02-24 09:09:38 +0900811 return '\n'.join(ret)
812
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900813 def GetLazyCalledByNativeMethodStubs(self):
814 return [self.GetLazyCalledByNativeMethodStub(called_by_native)
815 for called_by_native in self.called_by_natives]
816
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900817 def GetIncludesString(self):
818 if not self.options.includes:
819 return ''
820 includes = self.options.includes.split(',')
821 return '\n'.join('#include "%s"' % x for x in includes)
822
bulach@chromium.org6079a072012-02-24 09:09:38 +0900823 def GetKMethodsString(self, clazz):
824 ret = []
825 for native in self.natives:
826 if (native.java_class_name == clazz or
827 (not native.java_class_name and clazz == self.class_name)):
828 ret += [self.GetKMethodArrayEntry(native)]
829 return '\n'.join(ret)
830
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900831 def SubstituteNativeMethods(self, template):
Yipeng Wang97484ae2017-07-01 00:27:54 +0900832 """Substitutes JAVA_CLASS and KMETHODS in the provided template."""
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900833 ret = []
bulach@chromium.org6079a072012-02-24 09:09:38 +0900834 all_classes = self.GetUniqueClasses(self.natives)
835 all_classes[self.class_name] = self.fully_qualified_class
836 for clazz in all_classes:
837 kmethods = self.GetKMethodsString(clazz)
838 if kmethods:
Yipeng Wang97484ae2017-07-01 00:27:54 +0900839 values = {'JAVA_CLASS': clazz,
bulach@chromium.org6079a072012-02-24 09:09:38 +0900840 'KMETHODS': kmethods}
841 ret += [template.substitute(values)]
842 if not ret: return ''
843 return '\n' + '\n'.join(ret)
844
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900845 def GetJNINativeMethodsString(self):
846 """Returns the implementation of the array of native methods."""
tornefc32b002016-07-21 19:41:58 +0900847 if not self.options.native_exports_optional:
mkosiba@chromium.org56092f02014-08-11 20:27:12 +0900848 return ''
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900849 template = Template("""\
850static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
851${KMETHODS}
852};
853""")
854 return self.SubstituteNativeMethods(template)
855
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900856 def GetRegisterNativesString(self):
857 """Returns the code for RegisterNatives."""
sievers2c26d4b2016-08-04 08:20:40 +0900858 natives = self.GetRegisterNativesImplString()
859 if not natives:
860 return ''
Yipeng Wang97484ae2017-07-01 00:27:54 +0900861
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900862 template = Template("""\
Yipeng Wang97484ae2017-07-01 00:27:54 +0900863${REGISTER_NATIVES_SIGNATURE} {
864${EARLY_EXIT}
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900865${NATIVES}
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900866 return true;
867}
868""")
Yipeng Wang97484ae2017-07-01 00:27:54 +0900869 signature = 'static bool RegisterNativesImpl(JNIEnv* env)'
870 early_exit = ''
871 if self.options.native_exports_optional:
872 early_exit = """\
873 if (jni_generator::ShouldSkipJniRegistration(%s))
874 return true;
875""" % self.maindex
876
877 values = {'REGISTER_NATIVES_SIGNATURE': signature,
878 'EARLY_EXIT': early_exit,
879 'NATIVES': natives,
880 }
881
sievers2c26d4b2016-08-04 08:20:40 +0900882 return template.substitute(values)
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900883
884 def GetRegisterNativesImplString(self):
885 """Returns the shared implementation for RegisterNatives."""
tornefc32b002016-07-21 19:41:58 +0900886 if not self.options.native_exports_optional:
mkosiba@chromium.org56092f02014-08-11 20:27:12 +0900887 return ''
888
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900889 template = Template("""\
Yipeng Wang97484ae2017-07-01 00:27:54 +0900890 const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900891
mkosiba@chromium.org33075172014-08-22 01:37:32 +0900892 if (env->RegisterNatives(${JAVA_CLASS}_clazz(env),
Yipeng Wang97484ae2017-07-01 00:27:54 +0900893 kMethods${JAVA_CLASS},
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900894 kMethods${JAVA_CLASS}Size) < 0) {
895 jni_generator::HandleRegistrationError(
mkosiba@chromium.org33075172014-08-22 01:37:32 +0900896 env, ${JAVA_CLASS}_clazz(env), __FILE__);
bulach@chromium.org51dd7492014-01-08 23:41:13 +0900897 return false;
898 }
899""")
900 return self.SubstituteNativeMethods(template)
901
bulach@chromium.org6079a072012-02-24 09:09:38 +0900902 def GetOpenNamespaceString(self):
903 if self.namespace:
bulach@chromium.org16dde202012-07-24 00:31:35 +0900904 all_namespaces = ['namespace %s {' % ns
905 for ns in self.namespace.split('::')]
906 return '\n'.join(all_namespaces)
bulach@chromium.org6079a072012-02-24 09:09:38 +0900907 return ''
908
909 def GetCloseNamespaceString(self):
910 if self.namespace:
bulach@chromium.org16dde202012-07-24 00:31:35 +0900911 all_namespaces = ['} // namespace %s' % ns
912 for ns in self.namespace.split('::')]
913 all_namespaces.reverse()
914 return '\n'.join(all_namespaces) + '\n'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900915 return ''
916
tornec510c592015-09-04 20:16:35 +0900917 def GetJNIFirstParamType(self, native):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900918 if native.type == 'method':
tornec510c592015-09-04 20:16:35 +0900919 return 'jobject'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900920 elif native.type == 'function':
921 if native.static:
tornec510c592015-09-04 20:16:35 +0900922 return 'jclass'
bulach@chromium.org6079a072012-02-24 09:09:38 +0900923 else:
tornec510c592015-09-04 20:16:35 +0900924 return 'jobject'
925
926 def GetJNIFirstParam(self, native, for_declaration):
927 c_type = self.GetJNIFirstParamType(native)
928 if for_declaration:
929 c_type = WrapCTypeForDeclaration(c_type)
930 return [c_type + ' jcaller']
bulach@chromium.org6079a072012-02-24 09:09:38 +0900931
932 def GetParamsInDeclaration(self, native):
tornec510c592015-09-04 20:16:35 +0900933 """Returns the params for the forward declaration.
934
935 Args:
936 native: the native dictionary describing the method.
937
938 Returns:
939 A string containing the params.
940 """
941 return ',\n '.join(self.GetJNIFirstParam(native, True) +
942 [JavaDataTypeToCForDeclaration(param.datatype) + ' ' +
943 param.name
944 for param in native.params])
945
946 def GetParamsInStub(self, native):
bulach@chromium.org6079a072012-02-24 09:09:38 +0900947 """Returns the params for the stub declaration.
948
949 Args:
950 native: the native dictionary describing the method.
951
952 Returns:
953 A string containing the params.
954 """
tornec510c592015-09-04 20:16:35 +0900955 return ',\n '.join(self.GetJNIFirstParam(native, False) +
bulach@chromium.org6079a072012-02-24 09:09:38 +0900956 [JavaDataTypeToC(param.datatype) + ' ' +
957 param.name
958 for param in native.params])
959
960 def GetCalledByNativeParamsInDeclaration(self, called_by_native):
anton@chromium.org9d3b13b2014-05-07 23:14:10 +0900961 return ',\n '.join([
962 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
963 param.name
964 for param in called_by_native.params])
bulach@chromium.org6079a072012-02-24 09:09:38 +0900965
torne94bda722015-02-20 21:37:52 +0900966 def GetStubName(self, native):
967 """Return the name of the stub function for this native method.
968
969 Args:
970 native: the native dictionary describing the method.
971
972 Returns:
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +0900973 A string with the stub function name (used by the JVM).
torne94bda722015-02-20 21:37:52 +0900974 """
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +0900975 template = Template("Java_${JAVA_NAME}_native${NAME}")
torne94bda722015-02-20 21:37:52 +0900976
Yipeng Wang97484ae2017-07-01 00:27:54 +0900977 java_name = self.fully_qualified_class.replace('_', '_1').replace('/', '_')
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +0900978 if native.java_class_name:
979 java_name += '_00024' + native.java_class_name
torne94bda722015-02-20 21:37:52 +0900980
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +0900981 values = {'NAME': native.name,
982 'JAVA_NAME': java_name}
983 return template.substitute(values)
984
tornec510c592015-09-04 20:16:35 +0900985 def GetJavaParamRefForCall(self, c_type, name):
torne9854c722016-08-05 00:44:06 +0900986 return Template(
987 'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({
tornec510c592015-09-04 20:16:35 +0900988 'TYPE': c_type,
989 'NAME': name,
990 })
991
992 def GetJNIFirstParamForCall(self, native):
993 c_type = self.GetJNIFirstParamType(native)
994 return [self.GetJavaParamRefForCall(c_type, 'jcaller')]
995
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +0900996 def GetNativeStub(self, native):
997 is_method = native.type == 'method'
998
999 if is_method:
1000 params = native.params[1:]
torne94bda722015-02-20 21:37:52 +09001001 else:
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001002 params = native.params
torneb66773d2016-05-10 02:08:20 +09001003 params_in_call = ['env'] + self.GetJNIFirstParamForCall(native)
tornec510c592015-09-04 20:16:35 +09001004 for p in params:
1005 c_type = JavaDataTypeToC(p.datatype)
1006 if re.match(RE_SCOPED_JNI_TYPES, c_type):
1007 params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name))
1008 else:
1009 params_in_call.append(p.name)
1010 params_in_call = ', '.join(params_in_call)
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001011
torne41495262015-08-26 23:07:32 +09001012 return_type = return_declaration = JavaDataTypeToC(native.return_type)
1013 post_call = ''
tornec510c592015-09-04 20:16:35 +09001014 if re.match(RE_SCOPED_JNI_TYPES, return_type):
torne41495262015-08-26 23:07:32 +09001015 post_call = '.Release()'
torne9854c722016-08-05 00:44:06 +09001016 return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type +
1017 '>')
dskibab7cdc892016-10-26 08:39:11 +09001018 profiling_entered_native = ''
1019 if self.options.enable_profiling:
1020 profiling_entered_native = 'JNI_LINK_SAVED_FRAME_POINTER;'
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001021 values = {
1022 'RETURN': return_type,
torne41495262015-08-26 23:07:32 +09001023 'RETURN_DECLARATION': return_declaration,
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001024 'NAME': native.name,
1025 'PARAMS': self.GetParamsInDeclaration(native),
tornec510c592015-09-04 20:16:35 +09001026 'PARAMS_IN_STUB': self.GetParamsInStub(native),
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001027 'PARAMS_IN_CALL': params_in_call,
torne41495262015-08-26 23:07:32 +09001028 'POST_CALL': post_call,
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001029 'STUB_NAME': self.GetStubName(native),
dskibab7cdc892016-10-26 08:39:11 +09001030 'PROFILING_ENTERED_NATIVE': profiling_entered_native,
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001031 }
1032
1033 if is_method:
1034 optional_error_return = JavaReturnValueToC(native.return_type)
1035 if optional_error_return:
1036 optional_error_return = ', ' + optional_error_return
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001037 values.update({
1038 'OPTIONAL_ERROR_RETURN': optional_error_return,
1039 'PARAM0_NAME': native.params[0].name,
1040 'P0_TYPE': native.p0_type,
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001041 })
1042 template = Template("""\
torne17bc9642016-12-02 02:11:54 +09001043JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
dskibab7cdc892016-10-26 08:39:11 +09001044 ${PROFILING_ENTERED_NATIVE}
bulach@chromium.org6079a072012-02-24 09:09:38 +09001045 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001046 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
1047 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
bulach@chromium.org6079a072012-02-24 09:09:38 +09001048}
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001049""")
1050 else:
1051 template = Template("""
torne41495262015-08-26 23:07:32 +09001052static ${RETURN_DECLARATION} ${NAME}(JNIEnv* env, ${PARAMS});
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001053
torne17bc9642016-12-02 02:11:54 +09001054JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
dskibab7cdc892016-10-26 08:39:11 +09001055 ${PROFILING_ENTERED_NATIVE}
torne41495262015-08-26 23:07:32 +09001056 return ${NAME}(${PARAMS_IN_CALL})${POST_CALL};
Torne (Richard Coles)0715a9e2015-08-14 21:41:14 +09001057}
1058""")
bulach@chromium.org6079a072012-02-24 09:09:38 +09001059
dskibab7cdc892016-10-26 08:39:11 +09001060 return RemoveIndentedEmptyLines(template.substitute(values))
bulach@chromium.org6079a072012-02-24 09:09:38 +09001061
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001062 def GetArgument(self, param):
torne28061e92016-08-09 01:29:18 +09001063 if param.datatype == 'int':
1064 return 'as_jint(' + param.name + ')'
1065 elif re.match(RE_SCOPED_JNI_TYPES, JavaDataTypeToC(param.datatype)):
1066 return param.name + '.obj()'
1067 else:
1068 return param.name
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001069
1070 def GetArgumentsInCall(self, params):
1071 """Return a string of arguments to call from native into Java"""
1072 return [self.GetArgument(p) for p in params]
1073
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001074 def GetCalledByNativeValues(self, called_by_native):
1075 """Fills in necessary values for the CalledByNative methods."""
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001076 java_class = called_by_native.java_class_name or self.class_name
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001077 if called_by_native.static or called_by_native.is_constructor:
1078 first_param_in_declaration = ''
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001079 first_param_in_call = ('%s_clazz(env)' % java_class)
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001080 else:
torne28061e92016-08-09 01:29:18 +09001081 first_param_in_declaration = (
1082 ', const base::android::JavaRefOrBare<jobject>& obj')
1083 first_param_in_call = 'obj.obj()'
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001084 params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
1085 called_by_native)
1086 if params_in_declaration:
1087 params_in_declaration = ', ' + params_in_declaration
anton@chromium.org9d3b13b2014-05-07 23:14:10 +09001088 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001089 if params_in_call:
1090 params_in_call = ', ' + params_in_call
1091 pre_call = ''
1092 post_call = ''
1093 if called_by_native.static_cast:
1094 pre_call = 'static_cast<%s>(' % called_by_native.static_cast
1095 post_call = ')'
1096 check_exception = ''
1097 if not called_by_native.unchecked:
1098 check_exception = 'jni_generator::CheckException(env);'
1099 return_type = JavaDataTypeToC(called_by_native.return_type)
1100 optional_error_return = JavaReturnValueToC(called_by_native.return_type)
1101 if optional_error_return:
1102 optional_error_return = ', ' + optional_error_return
1103 return_declaration = ''
1104 return_clause = ''
1105 if return_type != 'void':
1106 pre_call = ' ' + pre_call
1107 return_declaration = return_type + ' ret ='
tornec510c592015-09-04 20:16:35 +09001108 if re.match(RE_SCOPED_JNI_TYPES, return_type):
torne9854c722016-08-05 00:44:06 +09001109 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001110 return_clause = 'return ' + return_type + '(env, ret);'
1111 else:
1112 return_clause = 'return ret;'
dskibab7cdc892016-10-26 08:39:11 +09001113 profiling_leaving_native = ''
1114 if self.options.enable_profiling:
1115 profiling_leaving_native = 'JNI_SAVE_FRAME_POINTER;'
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001116 return {
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001117 'JAVA_CLASS': java_class,
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001118 'RETURN_TYPE': return_type,
1119 'OPTIONAL_ERROR_RETURN': optional_error_return,
1120 'RETURN_DECLARATION': return_declaration,
1121 'RETURN_CLAUSE': return_clause,
1122 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
1123 'PARAMS_IN_DECLARATION': params_in_declaration,
1124 'PRE_CALL': pre_call,
1125 'POST_CALL': post_call,
1126 'ENV_CALL': called_by_native.env_call,
1127 'FIRST_PARAM_IN_CALL': first_param_in_call,
1128 'PARAMS_IN_CALL': params_in_call,
1129 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
1130 'CHECK_EXCEPTION': check_exception,
dskibab7cdc892016-10-26 08:39:11 +09001131 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native),
1132 'PROFILING_LEAVING_NATIVE': profiling_leaving_native,
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001133 }
1134
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001135
1136 def GetLazyCalledByNativeMethodStub(self, called_by_native):
bulach@chromium.org6079a072012-02-24 09:09:38 +09001137 """Returns a string."""
1138 function_signature_template = Template("""\
bulach@chromium.org0c6805b2012-09-28 21:34:33 +09001139static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
bulach@chromium.org6079a072012-02-24 09:09:38 +09001140JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
1141 function_header_template = Template("""\
1142${FUNCTION_SIGNATURE} {""")
1143 function_header_with_unused_template = Template("""\
1144${FUNCTION_SIGNATURE} __attribute__ ((unused));
1145${FUNCTION_SIGNATURE} {""")
1146 template = Template("""
bulach@chromium.org231cff62012-10-17 03:35:10 +09001147static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
bulach@chromium.org6079a072012-02-24 09:09:38 +09001148${FUNCTION_HEADER}
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001149 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001150 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN});
bulach@chromium.org231cff62012-10-17 03:35:10 +09001151 jmethodID method_id =
1152 ${GET_METHOD_ID_IMPL}
dskibab7cdc892016-10-26 08:39:11 +09001153 ${PROFILING_LEAVING_NATIVE}
bulach@chromium.org6079a072012-02-24 09:09:38 +09001154 ${RETURN_DECLARATION}
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001155 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
1156 method_id${PARAMS_IN_CALL})${POST_CALL};
bulach@chromium.org6079a072012-02-24 09:09:38 +09001157 ${CHECK_EXCEPTION}
1158 ${RETURN_CLAUSE}
1159}""")
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001160 values = self.GetCalledByNativeValues(called_by_native)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001161 values['FUNCTION_SIGNATURE'] = (
1162 function_signature_template.substitute(values))
1163 if called_by_native.system_class:
1164 values['FUNCTION_HEADER'] = (
1165 function_header_with_unused_template.substitute(values))
1166 else:
1167 values['FUNCTION_HEADER'] = function_header_template.substitute(values)
dskibab7cdc892016-10-26 08:39:11 +09001168 return RemoveIndentedEmptyLines(template.substitute(values))
bulach@chromium.org6079a072012-02-24 09:09:38 +09001169
1170 def GetKMethodArrayEntry(self, native):
torne94bda722015-02-20 21:37:52 +09001171 template = Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' +
1172 'reinterpret_cast<void*>(${STUB_NAME}) },')
Yipeng Wang60438ac2017-06-17 06:08:33 +09001173 values = {
1174 'NAME': native.name,
1175 'JNI_SIGNATURE': self.jni_params.Signature(native.params,
1176 native.return_type, True),
1177 'STUB_NAME': self.GetStubName(native)
1178 }
bulach@chromium.org6079a072012-02-24 09:09:38 +09001179 return template.substitute(values)
1180
1181 def GetUniqueClasses(self, origin):
1182 ret = {self.class_name: self.fully_qualified_class}
1183 for entry in origin:
1184 class_name = self.class_name
1185 jni_class_path = self.fully_qualified_class
1186 if entry.java_class_name:
1187 class_name = entry.java_class_name
1188 jni_class_path = self.fully_qualified_class + '$' + class_name
1189 ret[class_name] = jni_class_path
1190 return ret
1191
1192 def GetClassPathDefinitions(self):
1193 """Returns the ClassPath constants."""
1194 ret = []
1195 template = Template("""\
bulach@chromium.org1a8e1552012-03-02 00:51:51 +09001196const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
tornefc32b002016-07-21 19:41:58 +09001197 all_classes = self.GetUniqueClasses(self.called_by_natives)
1198 if self.options.native_exports_optional:
1199 all_classes.update(self.GetUniqueClasses(self.natives))
mkosiba@chromium.org56092f02014-08-11 20:27:12 +09001200
bulach@chromium.org6079a072012-02-24 09:09:38 +09001201 for clazz in all_classes:
1202 values = {
1203 'JAVA_CLASS': clazz,
torneb66773d2016-05-10 02:08:20 +09001204 'JNI_CLASS_PATH': all_classes[clazz],
bulach@chromium.org6079a072012-02-24 09:09:38 +09001205 }
1206 ret += [template.substitute(values)]
1207 ret += ''
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001208
tornefc32b002016-07-21 19:41:58 +09001209 template = Template("""\
ygorshenin@chromium.org25171e52014-08-20 20:44:05 +09001210// Leaking this jclass as we cannot use LazyInstance from some threads.
torne09434e52015-01-09 04:48:31 +09001211base::subtle::AtomicWord g_${JAVA_CLASS}_clazz __attribute__((unused)) = 0;
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001212#define ${JAVA_CLASS}_clazz(env) \
1213base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \
1214&g_${JAVA_CLASS}_clazz)""")
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001215
Torne (Richard Coles)42ef7042016-07-19 20:36:09 +09001216 for clazz in all_classes:
bulach@chromium.org6079a072012-02-24 09:09:38 +09001217 values = {
1218 'JAVA_CLASS': clazz,
1219 }
ygorshenin@chromium.org25171e52014-08-20 20:44:05 +09001220 ret += [template.substitute(values)]
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001221
bulach@chromium.org6079a072012-02-24 09:09:38 +09001222 return '\n'.join(ret)
1223
bulach@chromium.org6079a072012-02-24 09:09:38 +09001224 def GetMethodIDImpl(self, called_by_native):
1225 """Returns the implementation of GetMethodID."""
torneb66773d2016-05-10 02:08:20 +09001226 template = Template("""\
bulach@chromium.org231cff62012-10-17 03:35:10 +09001227 base::android::MethodID::LazyGet<
1228 base::android::MethodID::TYPE_${STATIC}>(
mkosiba@chromium.org33075172014-08-22 01:37:32 +09001229 env, ${JAVA_CLASS}_clazz(env),
bulach@chromium.org231cff62012-10-17 03:35:10 +09001230 "${JNI_NAME}",
1231 ${JNI_SIGNATURE},
1232 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
bulach@chromium.org6079a072012-02-24 09:09:38 +09001233""")
bulach@chromium.org31af7532012-09-24 20:01:41 +09001234 jni_name = called_by_native.name
1235 jni_return_type = called_by_native.return_type
1236 if called_by_native.is_constructor:
1237 jni_name = '<init>'
1238 jni_return_type = 'void'
bulach@chromium.org37fc9112013-10-26 01:27:03 +09001239 if called_by_native.signature:
1240 signature = called_by_native.signature
1241 else:
Yipeng Wang60438ac2017-06-17 06:08:33 +09001242 signature = self.jni_params.Signature(called_by_native.params,
1243 jni_return_type, True)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001244 values = {
1245 'JAVA_CLASS': called_by_native.java_class_name or self.class_name,
bulach@chromium.org31af7532012-09-24 20:01:41 +09001246 'JNI_NAME': jni_name,
bulach@chromium.org6079a072012-02-24 09:09:38 +09001247 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
bulach@chromium.org231cff62012-10-17 03:35:10 +09001248 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE',
bulach@chromium.org37fc9112013-10-26 01:27:03 +09001249 'JNI_SIGNATURE': signature,
bulach@chromium.org6079a072012-02-24 09:09:38 +09001250 }
1251 return template.substitute(values)
1252
1253
1254def WrapOutput(output):
1255 ret = []
1256 for line in output.splitlines():
tedchoc@chromium.org72992182012-08-03 11:00:17 +09001257 # Do not wrap lines under 80 characters or preprocessor directives.
1258 if len(line) < 80 or line.lstrip()[:1] == '#':
bulach@chromium.org1c775752012-06-22 19:03:16 +09001259 stripped = line.rstrip()
1260 if len(ret) == 0 or len(ret[-1]) or len(stripped):
1261 ret.append(stripped)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001262 else:
1263 first_line_indent = ' ' * (len(line) - len(line.lstrip()))
1264 subsequent_indent = first_line_indent + ' ' * 4
1265 if line.startswith('//'):
1266 subsequent_indent = '//' + subsequent_indent
1267 wrapper = textwrap.TextWrapper(width=80,
1268 subsequent_indent=subsequent_indent,
1269 break_long_words=False)
1270 ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)]
1271 ret += ['']
1272 return '\n'.join(ret)
1273
1274
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001275def ExtractJarInputFile(jar_file, input_file, out_dir):
1276 """Extracts input file from jar and returns the filename.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001277
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001278 The input file is extracted to the same directory that the generated jni
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001279 headers will be placed in. This is passed as an argument to script.
1280
1281 Args:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001282 jar_file: the jar file containing the input files to extract.
1283 input_files: the list of files to extract from the jar file.
1284 out_dir: the name of the directories to extract to.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001285
1286 Returns:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001287 the name of extracted input file.
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001288 """
1289 jar_file = zipfile.ZipFile(jar_file)
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001290
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001291 out_dir = os.path.join(out_dir, os.path.dirname(input_file))
torne@chromium.org108c37d2013-03-08 22:52:50 +09001292 try:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001293 os.makedirs(out_dir)
torne@chromium.org108c37d2013-03-08 22:52:50 +09001294 except OSError as e:
1295 if e.errno != errno.EEXIST:
1296 raise
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001297 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
1298 with open(extracted_file_name, 'w') as outfile:
1299 outfile.write(jar_file.read(input_file))
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001300
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001301 return extracted_file_name
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001302
1303
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001304def GenerateJNIHeader(input_file, output_file, options):
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001305 try:
1306 if os.path.splitext(input_file)[1] == '.class':
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001307 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001308 content = jni_from_javap.GetContent()
bulach@chromium.org6079a072012-02-24 09:09:38 +09001309 else:
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001310 jni_from_java_source = JNIFromJavaSource.CreateFromFile(
1311 input_file, options)
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001312 content = jni_from_java_source.GetContent()
1313 except ParseError, e:
1314 print e
1315 sys.exit(1)
1316 if output_file:
Yipeng Wang97484ae2017-07-01 00:27:54 +09001317 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))):
1318 os.makedirs(os.path.dirname(os.path.abspath(output_file)))
1319 if options.optimize_generation and os.path.exists(output_file):
1320 with file(output_file, 'r') as f:
1321 existing_content = f.read()
1322 if existing_content == content:
1323 return
1324 with file(output_file, 'w') as f:
1325 f.write(content)
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001326 else:
paulmiller4d5b53e2015-05-30 02:24:37 +09001327 print content
bulach@chromium.org6079a072012-02-24 09:09:38 +09001328
1329
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001330def GetScriptName():
1331 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
1332 base_index = 0
1333 for idx, value in enumerate(script_components):
1334 if value == 'base' or value == 'third_party':
1335 base_index = idx
1336 break
1337 return os.sep.join(script_components[base_index:])
1338
1339
bulach@chromium.org6079a072012-02-24 09:09:38 +09001340def main(argv):
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001341 usage = """usage: %prog [OPTIONS]
bulach@chromium.org6079a072012-02-24 09:09:38 +09001342This script will parse the given java source code extracting the native
1343declarations and print the header file to stdout (or a file).
1344See SampleForTests.java for more details.
1345 """
1346 option_parser = optparse.OptionParser(usage=usage)
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +09001347 build_utils.AddDepfileOption(option_parser)
1348
cjhopman@chromium.orge607d252014-06-06 04:47:30 +09001349 option_parser.add_option('-j', '--jar_file', dest='jar_file',
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001350 help='Extract the list of input files from'
bulach@chromium.org7f85d462012-05-30 18:56:02 +09001351 ' a specified jar file.'
1352 ' Uses javap to extract the methods from a'
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001353 ' pre-compiled class. --input should point'
bulach@chromium.org6079a072012-02-24 09:09:38 +09001354 ' to pre-compiled Java .class files.')
1355 option_parser.add_option('-n', dest='namespace',
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001356 help='Uses as a namespace in the generated header '
1357 'instead of the javap class name, or when there is '
1358 'no JNINamespace annotation in the java source.')
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001359 option_parser.add_option('--input_file',
1360 help='Single input file name. The output file name '
1361 'will be derived from it. Must be used with '
1362 '--output_dir.')
1363 option_parser.add_option('--output_dir',
1364 help='The output directory. Must be used with '
1365 '--input')
Yipeng Wang97484ae2017-07-01 00:27:54 +09001366 option_parser.add_option('--optimize_generation', type="int",
1367 default=0, help='Whether we should optimize JNI '
1368 'generation by not regenerating files if they have '
1369 'not changed.')
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001370 option_parser.add_option('--script_name', default=GetScriptName(),
1371 help='The name of this script in the generated '
1372 'header.')
bulach@chromium.org51dd7492014-01-08 23:41:13 +09001373 option_parser.add_option('--includes',
1374 help='The comma-separated list of header files to '
1375 'include in the generated header.')
bulach@chromium.orga022cf62013-11-05 09:54:22 +09001376 option_parser.add_option('--ptr_type', default='int',
1377 type='choice', choices=['int', 'long'],
1378 help='The type used to represent native pointers in '
1379 'Java code. For 32-bit, use int; '
1380 'for 64-bit, use long.')
bulach@chromium.org92d0dac2014-01-17 01:08:05 +09001381 option_parser.add_option('--cpp', default='cpp',
1382 help='The path to cpp command.')
1383 option_parser.add_option('--javap', default='javap',
1384 help='The path to javap command.')
torne0dd0b0d2015-02-21 08:06:33 +09001385 option_parser.add_option('--native_exports_optional', action='store_true',
1386 help='Support both explicit and native method'
1387 'registration.')
dskibab7cdc892016-10-26 08:39:11 +09001388 option_parser.add_option('--enable_profiling', action='store_true',
1389 help='Add additional profiling instrumentation.')
bulach@chromium.org6079a072012-02-24 09:09:38 +09001390 options, args = option_parser.parse_args(argv)
yfriedman@chromium.org28e6a042012-05-24 02:53:53 +09001391 if options.jar_file:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001392 input_file = ExtractJarInputFile(options.jar_file, options.input_file,
1393 options.output_dir)
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001394 elif options.input_file:
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001395 input_file = options.input_file
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001396 else:
1397 option_parser.print_help()
1398 print '\nError: Must specify --jar_file or --input_file.'
1399 return 1
bulach@chromium.org4c248ed2012-07-20 05:02:55 +09001400 output_file = None
1401 if options.output_dir:
1402 root_name = os.path.splitext(os.path.basename(input_file))[0]
1403 output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
bulach@chromium.org7cd403c2013-10-03 03:11:24 +09001404 GenerateJNIHeader(input_file, output_file, options)
bulach@chromium.org6079a072012-02-24 09:09:38 +09001405
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +09001406 if options.depfile:
agrieve92aed192016-09-14 11:04:21 +09001407 build_utils.WriteDepfile(options.depfile, output_file)
cjhopman@chromium.orgfb98e332014-06-25 08:38:17 +09001408
bulach@chromium.org6079a072012-02-24 09:09:38 +09001409
1410if __name__ == '__main__':
1411 sys.exit(main(sys.argv))