xref: /aosp_15_r20/external/cronet/third_party/jni_zero/parse.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker# Copyright 2023 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Workerimport dataclasses
5*6777b538SAndroid Build Coastguard Workerimport os
6*6777b538SAndroid Build Coastguard Workerimport re
7*6777b538SAndroid Build Coastguard Workerfrom typing import List
8*6777b538SAndroid Build Coastguard Workerfrom typing import Optional
9*6777b538SAndroid Build Coastguard Worker
10*6777b538SAndroid Build Coastguard Workerimport java_types
11*6777b538SAndroid Build Coastguard Worker
12*6777b538SAndroid Build Coastguard Worker_MODIFIER_KEYWORDS = (r'(?:(?:' + '|'.join([
13*6777b538SAndroid Build Coastguard Worker    'abstract',
14*6777b538SAndroid Build Coastguard Worker    'default',
15*6777b538SAndroid Build Coastguard Worker    'final',
16*6777b538SAndroid Build Coastguard Worker    'native',
17*6777b538SAndroid Build Coastguard Worker    'private',
18*6777b538SAndroid Build Coastguard Worker    'protected',
19*6777b538SAndroid Build Coastguard Worker    'public',
20*6777b538SAndroid Build Coastguard Worker    'static',
21*6777b538SAndroid Build Coastguard Worker    'synchronized',
22*6777b538SAndroid Build Coastguard Worker]) + r')\s+)*')
23*6777b538SAndroid Build Coastguard Worker
24*6777b538SAndroid Build Coastguard Worker
25*6777b538SAndroid Build Coastguard Workerclass ParseError(Exception):
26*6777b538SAndroid Build Coastguard Worker  suffix = ''
27*6777b538SAndroid Build Coastguard Worker
28*6777b538SAndroid Build Coastguard Worker  def __str__(self):
29*6777b538SAndroid Build Coastguard Worker    return super().__str__() + self.suffix
30*6777b538SAndroid Build Coastguard Worker
31*6777b538SAndroid Build Coastguard Worker
32*6777b538SAndroid Build Coastguard Worker@dataclasses.dataclass(order=True)
33*6777b538SAndroid Build Coastguard Workerclass ParsedNative:
34*6777b538SAndroid Build Coastguard Worker  name: str
35*6777b538SAndroid Build Coastguard Worker  signature: java_types.JavaSignature
36*6777b538SAndroid Build Coastguard Worker  native_class_name: str
37*6777b538SAndroid Build Coastguard Worker  static: bool = False
38*6777b538SAndroid Build Coastguard Worker
39*6777b538SAndroid Build Coastguard Worker
40*6777b538SAndroid Build Coastguard Worker@dataclasses.dataclass(order=True)
41*6777b538SAndroid Build Coastguard Workerclass ParsedCalledByNative:
42*6777b538SAndroid Build Coastguard Worker  java_class: java_types.JavaClass
43*6777b538SAndroid Build Coastguard Worker  name: str
44*6777b538SAndroid Build Coastguard Worker  signature: java_types.JavaSignature
45*6777b538SAndroid Build Coastguard Worker  static: bool
46*6777b538SAndroid Build Coastguard Worker  unchecked: bool = False
47*6777b538SAndroid Build Coastguard Worker
48*6777b538SAndroid Build Coastguard Worker
49*6777b538SAndroid Build Coastguard Worker@dataclasses.dataclass(order=True)
50*6777b538SAndroid Build Coastguard Workerclass ParsedConstantField(object):
51*6777b538SAndroid Build Coastguard Worker  name: str
52*6777b538SAndroid Build Coastguard Worker  value: str
53*6777b538SAndroid Build Coastguard Worker
54*6777b538SAndroid Build Coastguard Worker
55*6777b538SAndroid Build Coastguard Worker@dataclasses.dataclass
56*6777b538SAndroid Build Coastguard Workerclass ParsedFile:
57*6777b538SAndroid Build Coastguard Worker  filename: str
58*6777b538SAndroid Build Coastguard Worker  type_resolver: java_types.TypeResolver
59*6777b538SAndroid Build Coastguard Worker  proxy_methods: List[ParsedNative]
60*6777b538SAndroid Build Coastguard Worker  non_proxy_methods: List[ParsedNative]
61*6777b538SAndroid Build Coastguard Worker  called_by_natives: List[ParsedCalledByNative]
62*6777b538SAndroid Build Coastguard Worker  constant_fields: List[ParsedConstantField]
63*6777b538SAndroid Build Coastguard Worker  proxy_interface: Optional[java_types.JavaClass] = None
64*6777b538SAndroid Build Coastguard Worker  proxy_visibility: Optional[str] = None
65*6777b538SAndroid Build Coastguard Worker  module_name: Optional[str] = None  # E.g. @NativeMethods("module_name")
66*6777b538SAndroid Build Coastguard Worker  jni_namespace: Optional[str] = None  # E.g. @JNINamespace("content")
67*6777b538SAndroid Build Coastguard Worker
68*6777b538SAndroid Build Coastguard Worker
69*6777b538SAndroid Build Coastguard Worker@dataclasses.dataclass
70*6777b538SAndroid Build Coastguard Workerclass _ParsedProxyNatives:
71*6777b538SAndroid Build Coastguard Worker  interface_name: str
72*6777b538SAndroid Build Coastguard Worker  visibility: str
73*6777b538SAndroid Build Coastguard Worker  module_name: str
74*6777b538SAndroid Build Coastguard Worker  methods: List[ParsedNative]
75*6777b538SAndroid Build Coastguard Worker
76*6777b538SAndroid Build Coastguard Worker
77*6777b538SAndroid Build Coastguard Worker# Match single line comments, multiline comments, character literals, and
78*6777b538SAndroid Build Coastguard Worker# double-quoted strings.
79*6777b538SAndroid Build Coastguard Worker_COMMENT_REMOVER_REGEX = re.compile(
80*6777b538SAndroid Build Coastguard Worker    r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
81*6777b538SAndroid Build Coastguard Worker    re.DOTALL | re.MULTILINE)
82*6777b538SAndroid Build Coastguard Worker
83*6777b538SAndroid Build Coastguard Worker
84*6777b538SAndroid Build Coastguard Workerdef _remove_comments(contents):
85*6777b538SAndroid Build Coastguard Worker  # We need to support both inline and block comments, and we need to handle
86*6777b538SAndroid Build Coastguard Worker  # strings that contain '//' or '/*'.
87*6777b538SAndroid Build Coastguard Worker  def replacer(match):
88*6777b538SAndroid Build Coastguard Worker    # Replace matches that are comments with nothing; return literals/strings
89*6777b538SAndroid Build Coastguard Worker    # unchanged.
90*6777b538SAndroid Build Coastguard Worker    s = match.group(0)
91*6777b538SAndroid Build Coastguard Worker    if s.startswith('/'):
92*6777b538SAndroid Build Coastguard Worker      return ''
93*6777b538SAndroid Build Coastguard Worker    else:
94*6777b538SAndroid Build Coastguard Worker      return s
95*6777b538SAndroid Build Coastguard Worker
96*6777b538SAndroid Build Coastguard Worker  return _COMMENT_REMOVER_REGEX.sub(replacer, contents)
97*6777b538SAndroid Build Coastguard Worker
98*6777b538SAndroid Build Coastguard Worker
99*6777b538SAndroid Build Coastguard Worker# Remove everything between and including <> except at the end of a string, e.g.
100*6777b538SAndroid Build Coastguard Worker# @JniType("std::vector<int>")
101*6777b538SAndroid Build Coastguard Worker# This will also break lines with comparison operators, but we don't care.
102*6777b538SAndroid Build Coastguard Worker_GENERICS_REGEX = re.compile(r'<[^<>\n]*>(?!>*")')
103*6777b538SAndroid Build Coastguard Worker
104*6777b538SAndroid Build Coastguard Worker
105*6777b538SAndroid Build Coastguard Workerdef _remove_generics(value):
106*6777b538SAndroid Build Coastguard Worker  """Strips Java generics from a string."""
107*6777b538SAndroid Build Coastguard Worker  while True:
108*6777b538SAndroid Build Coastguard Worker    ret = _GENERICS_REGEX.sub('', value)
109*6777b538SAndroid Build Coastguard Worker    if len(ret) == len(value):
110*6777b538SAndroid Build Coastguard Worker      return ret
111*6777b538SAndroid Build Coastguard Worker    value = ret
112*6777b538SAndroid Build Coastguard Worker
113*6777b538SAndroid Build Coastguard Worker
114*6777b538SAndroid Build Coastguard Worker_PACKAGE_REGEX = re.compile('^package\s+(\S+?);', flags=re.MULTILINE)
115*6777b538SAndroid Build Coastguard Worker
116*6777b538SAndroid Build Coastguard Worker
117*6777b538SAndroid Build Coastguard Workerdef _parse_package(contents):
118*6777b538SAndroid Build Coastguard Worker  match = _PACKAGE_REGEX.search(contents)
119*6777b538SAndroid Build Coastguard Worker  if not match:
120*6777b538SAndroid Build Coastguard Worker    raise ParseError('Unable to find "package" line')
121*6777b538SAndroid Build Coastguard Worker  return match.group(1)
122*6777b538SAndroid Build Coastguard Worker
123*6777b538SAndroid Build Coastguard Worker
124*6777b538SAndroid Build Coastguard Worker_CLASSES_REGEX = re.compile(
125*6777b538SAndroid Build Coastguard Worker    r'^(.*?)(?:\b(?:public|protected|private)?\b)\s*'
126*6777b538SAndroid Build Coastguard Worker    r'(?:\b(?:static|abstract|final|sealed)\s+)*'
127*6777b538SAndroid Build Coastguard Worker    r'\b(?:class|interface|enum)\s+(\w+?)\b[^"]*?$',
128*6777b538SAndroid Build Coastguard Worker    flags=re.MULTILINE)
129*6777b538SAndroid Build Coastguard Worker
130*6777b538SAndroid Build Coastguard Worker
131*6777b538SAndroid Build Coastguard Worker# Does not handle doubly-nested classes.
132*6777b538SAndroid Build Coastguard Workerdef _parse_java_classes(contents):
133*6777b538SAndroid Build Coastguard Worker  package = _parse_package(contents).replace('.', '/')
134*6777b538SAndroid Build Coastguard Worker  outer_class = None
135*6777b538SAndroid Build Coastguard Worker  nested_classes = []
136*6777b538SAndroid Build Coastguard Worker  for m in _CLASSES_REGEX.finditer(contents):
137*6777b538SAndroid Build Coastguard Worker    preamble, class_name = m.groups()
138*6777b538SAndroid Build Coastguard Worker    # Ignore annotations like @Foo("contains the words class Bar")
139*6777b538SAndroid Build Coastguard Worker    if preamble.count('"') % 2 != 0:
140*6777b538SAndroid Build Coastguard Worker      continue
141*6777b538SAndroid Build Coastguard Worker    if outer_class is None:
142*6777b538SAndroid Build Coastguard Worker      outer_class = java_types.JavaClass(f'{package}/{class_name}')
143*6777b538SAndroid Build Coastguard Worker    else:
144*6777b538SAndroid Build Coastguard Worker      nested_classes.append(outer_class.make_nested(class_name))
145*6777b538SAndroid Build Coastguard Worker
146*6777b538SAndroid Build Coastguard Worker  if outer_class is None:
147*6777b538SAndroid Build Coastguard Worker    raise ParseError('No classes found.')
148*6777b538SAndroid Build Coastguard Worker
149*6777b538SAndroid Build Coastguard Worker  return outer_class, nested_classes
150*6777b538SAndroid Build Coastguard Worker
151*6777b538SAndroid Build Coastguard Worker
152*6777b538SAndroid Build Coastguard Worker_ANNOTATION_REGEX = re.compile(
153*6777b538SAndroid Build Coastguard Worker    r'@(?P<annotation_name>[\w.]+)(?P<annotation_args>\(\s*(?:[^)]+)\s*\))?\s*')
154*6777b538SAndroid Build Coastguard Worker# Only supports ("foo")
155*6777b538SAndroid Build Coastguard Worker_ANNOTATION_ARGS_REGEX = re.compile(
156*6777b538SAndroid Build Coastguard Worker    r'\(\s*"(?P<annotation_value>[^"]*?)"\s*\)\s*')
157*6777b538SAndroid Build Coastguard Worker
158*6777b538SAndroid Build Coastguard Workerdef _parse_annotations(value):
159*6777b538SAndroid Build Coastguard Worker  annotations = {}
160*6777b538SAndroid Build Coastguard Worker  last_idx = 0
161*6777b538SAndroid Build Coastguard Worker  for m in _ANNOTATION_REGEX.finditer(value):
162*6777b538SAndroid Build Coastguard Worker    string_value = ''
163*6777b538SAndroid Build Coastguard Worker    if match_args := m.group('annotation_args'):
164*6777b538SAndroid Build Coastguard Worker      if match_arg_value := _ANNOTATION_ARGS_REGEX.match(match_args):
165*6777b538SAndroid Build Coastguard Worker        string_value = match_arg_value.group('annotation_value')
166*6777b538SAndroid Build Coastguard Worker    annotations[m.group('annotation_name')] = string_value
167*6777b538SAndroid Build Coastguard Worker    last_idx = m.end()
168*6777b538SAndroid Build Coastguard Worker
169*6777b538SAndroid Build Coastguard Worker  return annotations, value[last_idx:]
170*6777b538SAndroid Build Coastguard Worker
171*6777b538SAndroid Build Coastguard Worker
172*6777b538SAndroid Build Coastguard Workerdef _parse_type(type_resolver, value):
173*6777b538SAndroid Build Coastguard Worker  """Parses a string into a JavaType."""
174*6777b538SAndroid Build Coastguard Worker  annotations, value = _parse_annotations(value)
175*6777b538SAndroid Build Coastguard Worker  array_dimensions = 0
176*6777b538SAndroid Build Coastguard Worker  while value[-2:] == '[]':
177*6777b538SAndroid Build Coastguard Worker    array_dimensions += 1
178*6777b538SAndroid Build Coastguard Worker    value = value[:-2]
179*6777b538SAndroid Build Coastguard Worker
180*6777b538SAndroid Build Coastguard Worker  if value in java_types.PRIMITIVES:
181*6777b538SAndroid Build Coastguard Worker    primitive_name = value
182*6777b538SAndroid Build Coastguard Worker    java_class = None
183*6777b538SAndroid Build Coastguard Worker  else:
184*6777b538SAndroid Build Coastguard Worker    primitive_name = None
185*6777b538SAndroid Build Coastguard Worker    java_class = type_resolver.resolve(value)
186*6777b538SAndroid Build Coastguard Worker
187*6777b538SAndroid Build Coastguard Worker  return java_types.JavaType(array_dimensions=array_dimensions,
188*6777b538SAndroid Build Coastguard Worker                             primitive_name=primitive_name,
189*6777b538SAndroid Build Coastguard Worker                             java_class=java_class,
190*6777b538SAndroid Build Coastguard Worker                             annotations=annotations)
191*6777b538SAndroid Build Coastguard Worker
192*6777b538SAndroid Build Coastguard Worker
193*6777b538SAndroid Build Coastguard Worker_FINAL_REGEX = re.compile(r'\bfinal\s')
194*6777b538SAndroid Build Coastguard Worker
195*6777b538SAndroid Build Coastguard Worker
196*6777b538SAndroid Build Coastguard Workerdef _parse_param_list(type_resolver, value) -> java_types.JavaParamList:
197*6777b538SAndroid Build Coastguard Worker  if not value or value.isspace():
198*6777b538SAndroid Build Coastguard Worker    return java_types.EMPTY_PARAM_LIST
199*6777b538SAndroid Build Coastguard Worker  params = []
200*6777b538SAndroid Build Coastguard Worker  value = _FINAL_REGEX.sub('', value)
201*6777b538SAndroid Build Coastguard Worker  for param_str in value.split(','):
202*6777b538SAndroid Build Coastguard Worker    param_str = param_str.strip()
203*6777b538SAndroid Build Coastguard Worker    param_str, _, param_name = param_str.rpartition(' ')
204*6777b538SAndroid Build Coastguard Worker    param_str = param_str.rstrip()
205*6777b538SAndroid Build Coastguard Worker
206*6777b538SAndroid Build Coastguard Worker    # Handle varargs.
207*6777b538SAndroid Build Coastguard Worker    if param_str.endswith('...'):
208*6777b538SAndroid Build Coastguard Worker      param_str = param_str[:-3] + '[]'
209*6777b538SAndroid Build Coastguard Worker
210*6777b538SAndroid Build Coastguard Worker    param_type = _parse_type(type_resolver, param_str)
211*6777b538SAndroid Build Coastguard Worker    params.append(java_types.JavaParam(param_type, param_name))
212*6777b538SAndroid Build Coastguard Worker
213*6777b538SAndroid Build Coastguard Worker  return java_types.JavaParamList(params)
214*6777b538SAndroid Build Coastguard Worker
215*6777b538SAndroid Build Coastguard Worker
216*6777b538SAndroid Build Coastguard Worker_NATIVE_METHODS_INTERFACE_REGEX = re.compile(
217*6777b538SAndroid Build Coastguard Worker    r'@NativeMethods(?:\(\s*"(?P<module_name>\w+)"\s*\))?[\S\s]+?'
218*6777b538SAndroid Build Coastguard Worker    r'(?P<visibility>public)?\s*\binterface\s*'
219*6777b538SAndroid Build Coastguard Worker    r'(?P<interface_name>\w*)\s*{(?P<interface_body>(\s*.*)+?\s*)}')
220*6777b538SAndroid Build Coastguard Worker
221*6777b538SAndroid Build Coastguard Worker_PROXY_NATIVE_REGEX = re.compile(r'\s*(.*?)\s+(\w+)\((.*?)\);', flags=re.DOTALL)
222*6777b538SAndroid Build Coastguard Worker
223*6777b538SAndroid Build Coastguard Worker_PUBLIC_REGEX = re.compile(r'\bpublic\s')
224*6777b538SAndroid Build Coastguard Worker
225*6777b538SAndroid Build Coastguard Worker
226*6777b538SAndroid Build Coastguard Workerdef _parse_proxy_natives(type_resolver, contents):
227*6777b538SAndroid Build Coastguard Worker  matches = list(_NATIVE_METHODS_INTERFACE_REGEX.finditer(contents))
228*6777b538SAndroid Build Coastguard Worker  if not matches:
229*6777b538SAndroid Build Coastguard Worker    return None
230*6777b538SAndroid Build Coastguard Worker  if len(matches) > 1:
231*6777b538SAndroid Build Coastguard Worker    raise ParseError(
232*6777b538SAndroid Build Coastguard Worker        'Multiple @NativeMethod interfaces in one class is not supported.')
233*6777b538SAndroid Build Coastguard Worker
234*6777b538SAndroid Build Coastguard Worker  match = matches[0]
235*6777b538SAndroid Build Coastguard Worker  ret = _ParsedProxyNatives(interface_name=match.group('interface_name'),
236*6777b538SAndroid Build Coastguard Worker                            visibility=match.group('visibility'),
237*6777b538SAndroid Build Coastguard Worker                            module_name=match.group('module_name'),
238*6777b538SAndroid Build Coastguard Worker                            methods=[])
239*6777b538SAndroid Build Coastguard Worker  interface_body = match.group('interface_body')
240*6777b538SAndroid Build Coastguard Worker
241*6777b538SAndroid Build Coastguard Worker  for m in _PROXY_NATIVE_REGEX.finditer(interface_body):
242*6777b538SAndroid Build Coastguard Worker    preamble, name, params_part = m.groups()
243*6777b538SAndroid Build Coastguard Worker    preamble = _PUBLIC_REGEX.sub('', preamble)
244*6777b538SAndroid Build Coastguard Worker    annotations, _ = _parse_annotations(preamble)
245*6777b538SAndroid Build Coastguard Worker    params = _parse_param_list(type_resolver, params_part)
246*6777b538SAndroid Build Coastguard Worker    return_type = _parse_type(type_resolver, preamble)
247*6777b538SAndroid Build Coastguard Worker    signature = java_types.JavaSignature.from_params(return_type, params)
248*6777b538SAndroid Build Coastguard Worker    ret.methods.append(
249*6777b538SAndroid Build Coastguard Worker        ParsedNative(
250*6777b538SAndroid Build Coastguard Worker            name=name,
251*6777b538SAndroid Build Coastguard Worker            signature=signature,
252*6777b538SAndroid Build Coastguard Worker            native_class_name=annotations.get('NativeClassQualifiedName')))
253*6777b538SAndroid Build Coastguard Worker  if not ret.methods:
254*6777b538SAndroid Build Coastguard Worker    raise ParseError('Found no methods within @NativeMethod interface.')
255*6777b538SAndroid Build Coastguard Worker  ret.methods.sort()
256*6777b538SAndroid Build Coastguard Worker  return ret
257*6777b538SAndroid Build Coastguard Worker
258*6777b538SAndroid Build Coastguard Worker
259*6777b538SAndroid Build Coastguard Worker_NON_PROXY_NATIVES_REGEX = re.compile(
260*6777b538SAndroid Build Coastguard Worker    r'(@NativeClassQualifiedName'
261*6777b538SAndroid Build Coastguard Worker    r'\(\"(?P<native_class_name>\S*?)\"\)\s+)?'
262*6777b538SAndroid Build Coastguard Worker    r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native\s+'
263*6777b538SAndroid Build Coastguard Worker    r'(?P<return_type>\S*)\s+'
264*6777b538SAndroid Build Coastguard Worker    r'(?P<name>native\w+)\((?P<params>.*?)\);', re.DOTALL)
265*6777b538SAndroid Build Coastguard Worker
266*6777b538SAndroid Build Coastguard Worker
267*6777b538SAndroid Build Coastguard Workerdef _parse_non_proxy_natives(type_resolver, contents):
268*6777b538SAndroid Build Coastguard Worker  ret = []
269*6777b538SAndroid Build Coastguard Worker  for match in _NON_PROXY_NATIVES_REGEX.finditer(contents):
270*6777b538SAndroid Build Coastguard Worker    name = match.group('name').replace('native', '')
271*6777b538SAndroid Build Coastguard Worker    return_type = _parse_type(type_resolver, match.group('return_type'))
272*6777b538SAndroid Build Coastguard Worker    params = _parse_param_list(type_resolver, match.group('params'))
273*6777b538SAndroid Build Coastguard Worker    signature = java_types.JavaSignature.from_params(return_type, params)
274*6777b538SAndroid Build Coastguard Worker    native_class_name = match.group('native_class_name')
275*6777b538SAndroid Build Coastguard Worker    static = 'static' in match.group('qualifiers')
276*6777b538SAndroid Build Coastguard Worker    ret.append(
277*6777b538SAndroid Build Coastguard Worker        ParsedNative(name=name,
278*6777b538SAndroid Build Coastguard Worker                     signature=signature,
279*6777b538SAndroid Build Coastguard Worker                     native_class_name=native_class_name,
280*6777b538SAndroid Build Coastguard Worker                     static=static))
281*6777b538SAndroid Build Coastguard Worker  ret.sort()
282*6777b538SAndroid Build Coastguard Worker  return ret
283*6777b538SAndroid Build Coastguard Worker
284*6777b538SAndroid Build Coastguard Worker
285*6777b538SAndroid Build Coastguard Worker# Regex to match a string like "@CalledByNative public void foo(int bar)".
286*6777b538SAndroid Build Coastguard Worker_CALLED_BY_NATIVE_REGEX = re.compile(
287*6777b538SAndroid Build Coastguard Worker    r'@CalledByNative((?P<Unchecked>(?:Unchecked)?|ForTesting))'
288*6777b538SAndroid Build Coastguard Worker    r'(?:\("(?P<annotation_value>.*)"\))?'
289*6777b538SAndroid Build Coastguard Worker    r'(?P<method_annotations>(?:\s*@\w+(?:\(.*?\))?)+)?'
290*6777b538SAndroid Build Coastguard Worker    r'\s+(?P<modifiers>' + _MODIFIER_KEYWORDS + r')' +
291*6777b538SAndroid Build Coastguard Worker    r'(?P<return_type_annotations>(?:\s*@\w+(?:\(.*?\))?)+)?'
292*6777b538SAndroid Build Coastguard Worker    r'\s*(?P<return_type>\S*?)'
293*6777b538SAndroid Build Coastguard Worker    r'\s*(?P<name>\w+)'
294*6777b538SAndroid Build Coastguard Worker    r'\s*\(\s*(?P<params>[^{;]*)\)'
295*6777b538SAndroid Build Coastguard Worker    r'\s*(?:throws\s+[^{;]+)?'
296*6777b538SAndroid Build Coastguard Worker    r'[{;]')
297*6777b538SAndroid Build Coastguard Worker
298*6777b538SAndroid Build Coastguard Worker
299*6777b538SAndroid Build Coastguard Workerdef _parse_called_by_natives(type_resolver, contents):
300*6777b538SAndroid Build Coastguard Worker  ret = []
301*6777b538SAndroid Build Coastguard Worker  for match in _CALLED_BY_NATIVE_REGEX.finditer(contents):
302*6777b538SAndroid Build Coastguard Worker    return_type_grp = match.group('return_type')
303*6777b538SAndroid Build Coastguard Worker    name = match.group('name')
304*6777b538SAndroid Build Coastguard Worker    if return_type_grp:
305*6777b538SAndroid Build Coastguard Worker      pre_annotations = match.group('method_annotations') or ''
306*6777b538SAndroid Build Coastguard Worker      post_annotations = match.group('return_type_annotations') or ''
307*6777b538SAndroid Build Coastguard Worker      # Combine all the annotations before parsing the return type.
308*6777b538SAndroid Build Coastguard Worker      return_type_str = str.strip(f'{pre_annotations} {post_annotations}'
309*6777b538SAndroid Build Coastguard Worker                                  f' {return_type_grp}')
310*6777b538SAndroid Build Coastguard Worker      return_type = _parse_type(type_resolver, return_type_str)
311*6777b538SAndroid Build Coastguard Worker    else:
312*6777b538SAndroid Build Coastguard Worker      return_type = java_types.VOID
313*6777b538SAndroid Build Coastguard Worker      name = '<init>'
314*6777b538SAndroid Build Coastguard Worker
315*6777b538SAndroid Build Coastguard Worker    params = _parse_param_list(type_resolver, match.group('params'))
316*6777b538SAndroid Build Coastguard Worker    signature = java_types.JavaSignature.from_params(return_type, params)
317*6777b538SAndroid Build Coastguard Worker    inner_class_name = match.group('annotation_value')
318*6777b538SAndroid Build Coastguard Worker    java_class = type_resolver.java_class
319*6777b538SAndroid Build Coastguard Worker    if inner_class_name:
320*6777b538SAndroid Build Coastguard Worker      java_class = java_class.make_nested(inner_class_name)
321*6777b538SAndroid Build Coastguard Worker
322*6777b538SAndroid Build Coastguard Worker    ret.append(
323*6777b538SAndroid Build Coastguard Worker        ParsedCalledByNative(java_class=java_class,
324*6777b538SAndroid Build Coastguard Worker                             name=name,
325*6777b538SAndroid Build Coastguard Worker                             signature=signature,
326*6777b538SAndroid Build Coastguard Worker                             static='static' in match.group('modifiers'),
327*6777b538SAndroid Build Coastguard Worker                             unchecked='Unchecked' in match.group('Unchecked')))
328*6777b538SAndroid Build Coastguard Worker
329*6777b538SAndroid Build Coastguard Worker  # Check for any @CalledByNative occurrences that were not matched.
330*6777b538SAndroid Build Coastguard Worker  unmatched_lines = _CALLED_BY_NATIVE_REGEX.sub('', contents).splitlines()
331*6777b538SAndroid Build Coastguard Worker  for i, line in enumerate(unmatched_lines):
332*6777b538SAndroid Build Coastguard Worker    if '@CalledByNative' in line:
333*6777b538SAndroid Build Coastguard Worker      context = '\n'.join(unmatched_lines[i:i + 5])
334*6777b538SAndroid Build Coastguard Worker      raise ParseError('Could not parse @CalledByNative method signature:\n' +
335*6777b538SAndroid Build Coastguard Worker                       context)
336*6777b538SAndroid Build Coastguard Worker
337*6777b538SAndroid Build Coastguard Worker  ret.sort()
338*6777b538SAndroid Build Coastguard Worker  return ret
339*6777b538SAndroid Build Coastguard Worker
340*6777b538SAndroid Build Coastguard Worker
341*6777b538SAndroid Build Coastguard Worker_IMPORT_REGEX = re.compile(r'^import\s+([^\s*]+);', flags=re.MULTILINE)
342*6777b538SAndroid Build Coastguard Worker_IMPORT_CLASS_NAME_REGEX = re.compile(r'^(.*?)\.([A-Z].*)')
343*6777b538SAndroid Build Coastguard Worker
344*6777b538SAndroid Build Coastguard Worker
345*6777b538SAndroid Build Coastguard Workerdef _parse_imports(contents):
346*6777b538SAndroid Build Coastguard Worker  # Regex skips static imports as well as wildcard imports.
347*6777b538SAndroid Build Coastguard Worker  names = _IMPORT_REGEX.findall(contents)
348*6777b538SAndroid Build Coastguard Worker  for name in names:
349*6777b538SAndroid Build Coastguard Worker    m = _IMPORT_CLASS_NAME_REGEX.match(name)
350*6777b538SAndroid Build Coastguard Worker    if m:
351*6777b538SAndroid Build Coastguard Worker      package, class_name = m.groups()
352*6777b538SAndroid Build Coastguard Worker      yield java_types.JavaClass(
353*6777b538SAndroid Build Coastguard Worker          package.replace('.', '/') + '/' + class_name.replace('.', '$'))
354*6777b538SAndroid Build Coastguard Worker
355*6777b538SAndroid Build Coastguard Worker
356*6777b538SAndroid Build Coastguard Worker_JNI_NAMESPACE_REGEX = re.compile('@JNINamespace\("(.*?)"\)')
357*6777b538SAndroid Build Coastguard Worker
358*6777b538SAndroid Build Coastguard Worker
359*6777b538SAndroid Build Coastguard Workerdef _parse_jni_namespace(contents):
360*6777b538SAndroid Build Coastguard Worker  m = _JNI_NAMESPACE_REGEX.findall(contents)
361*6777b538SAndroid Build Coastguard Worker  if not m:
362*6777b538SAndroid Build Coastguard Worker    return ''
363*6777b538SAndroid Build Coastguard Worker  if len(m) > 1:
364*6777b538SAndroid Build Coastguard Worker    raise ParseError('Found multiple @JNINamespace annotations.')
365*6777b538SAndroid Build Coastguard Worker  return m[0]
366*6777b538SAndroid Build Coastguard Worker
367*6777b538SAndroid Build Coastguard Worker
368*6777b538SAndroid Build Coastguard Workerdef _do_parse(filename, *, package_prefix):
369*6777b538SAndroid Build Coastguard Worker  assert not filename.endswith('.kt'), (
370*6777b538SAndroid Build Coastguard Worker      f'Found {filename}, but Kotlin is not supported by JNI generator.')
371*6777b538SAndroid Build Coastguard Worker  with open(filename) as f:
372*6777b538SAndroid Build Coastguard Worker    contents = f.read()
373*6777b538SAndroid Build Coastguard Worker  contents = _remove_comments(contents)
374*6777b538SAndroid Build Coastguard Worker  contents = _remove_generics(contents)
375*6777b538SAndroid Build Coastguard Worker
376*6777b538SAndroid Build Coastguard Worker  outer_class, nested_classes = _parse_java_classes(contents)
377*6777b538SAndroid Build Coastguard Worker
378*6777b538SAndroid Build Coastguard Worker  expected_name = os.path.splitext(os.path.basename(filename))[0]
379*6777b538SAndroid Build Coastguard Worker  if outer_class.name != expected_name:
380*6777b538SAndroid Build Coastguard Worker    raise ParseError(
381*6777b538SAndroid Build Coastguard Worker        f'Found class "{outer_class.name}" but expected "{expected_name}".')
382*6777b538SAndroid Build Coastguard Worker
383*6777b538SAndroid Build Coastguard Worker  if package_prefix:
384*6777b538SAndroid Build Coastguard Worker    outer_class = outer_class.make_prefixed(package_prefix)
385*6777b538SAndroid Build Coastguard Worker    nested_classes = [c.make_prefixed(package_prefix) for c in nested_classes]
386*6777b538SAndroid Build Coastguard Worker
387*6777b538SAndroid Build Coastguard Worker  type_resolver = java_types.TypeResolver(outer_class)
388*6777b538SAndroid Build Coastguard Worker  for java_class in _parse_imports(contents):
389*6777b538SAndroid Build Coastguard Worker    type_resolver.add_import(java_class)
390*6777b538SAndroid Build Coastguard Worker  for java_class in nested_classes:
391*6777b538SAndroid Build Coastguard Worker    type_resolver.add_nested_class(java_class)
392*6777b538SAndroid Build Coastguard Worker
393*6777b538SAndroid Build Coastguard Worker  parsed_proxy_natives = _parse_proxy_natives(type_resolver, contents)
394*6777b538SAndroid Build Coastguard Worker  jni_namespace = _parse_jni_namespace(contents)
395*6777b538SAndroid Build Coastguard Worker
396*6777b538SAndroid Build Coastguard Worker  non_proxy_methods = _parse_non_proxy_natives(type_resolver, contents)
397*6777b538SAndroid Build Coastguard Worker  called_by_natives = _parse_called_by_natives(type_resolver, contents)
398*6777b538SAndroid Build Coastguard Worker
399*6777b538SAndroid Build Coastguard Worker  ret = ParsedFile(filename=filename,
400*6777b538SAndroid Build Coastguard Worker                   jni_namespace=jni_namespace,
401*6777b538SAndroid Build Coastguard Worker                   type_resolver=type_resolver,
402*6777b538SAndroid Build Coastguard Worker                   proxy_methods=[],
403*6777b538SAndroid Build Coastguard Worker                   non_proxy_methods=non_proxy_methods,
404*6777b538SAndroid Build Coastguard Worker                   called_by_natives=called_by_natives,
405*6777b538SAndroid Build Coastguard Worker                   constant_fields=[])
406*6777b538SAndroid Build Coastguard Worker
407*6777b538SAndroid Build Coastguard Worker  if parsed_proxy_natives:
408*6777b538SAndroid Build Coastguard Worker    ret.module_name = parsed_proxy_natives.module_name
409*6777b538SAndroid Build Coastguard Worker    ret.proxy_interface = outer_class.make_nested(
410*6777b538SAndroid Build Coastguard Worker        parsed_proxy_natives.interface_name)
411*6777b538SAndroid Build Coastguard Worker    ret.proxy_visibility = parsed_proxy_natives.visibility
412*6777b538SAndroid Build Coastguard Worker    ret.proxy_methods = parsed_proxy_natives.methods
413*6777b538SAndroid Build Coastguard Worker
414*6777b538SAndroid Build Coastguard Worker  return ret
415*6777b538SAndroid Build Coastguard Worker
416*6777b538SAndroid Build Coastguard Worker
417*6777b538SAndroid Build Coastguard Workerdef parse_java_file(filename, *, package_prefix=None):
418*6777b538SAndroid Build Coastguard Worker  try:
419*6777b538SAndroid Build Coastguard Worker    return _do_parse(filename, package_prefix=package_prefix)
420*6777b538SAndroid Build Coastguard Worker  except ParseError as e:
421*6777b538SAndroid Build Coastguard Worker    e.suffix = f' (when parsing {filename})'
422*6777b538SAndroid Build Coastguard Worker    raise
423*6777b538SAndroid Build Coastguard Worker
424*6777b538SAndroid Build Coastguard Worker
425*6777b538SAndroid Build Coastguard Worker_JAVAP_CLASS_REGEX = re.compile(r'\b(?:class|interface) (\S+)')
426*6777b538SAndroid Build Coastguard Worker_JAVAP_FINAL_FIELD_REGEX = re.compile(
427*6777b538SAndroid Build Coastguard Worker    r'^\s+public static final \S+ (.*?) = (\d+);', flags=re.MULTILINE)
428*6777b538SAndroid Build Coastguard Worker_JAVAP_METHOD_REGEX = re.compile(
429*6777b538SAndroid Build Coastguard Worker    rf'^\s*({_MODIFIER_KEYWORDS}).*?(\S+?)\(.*\n\s+descriptor: (.*)',
430*6777b538SAndroid Build Coastguard Worker    flags=re.MULTILINE)
431*6777b538SAndroid Build Coastguard Worker
432*6777b538SAndroid Build Coastguard Worker
433*6777b538SAndroid Build Coastguard Workerdef parse_javap(filename, contents):
434*6777b538SAndroid Build Coastguard Worker  contents = _remove_generics(contents)
435*6777b538SAndroid Build Coastguard Worker  match = _JAVAP_CLASS_REGEX.search(contents)
436*6777b538SAndroid Build Coastguard Worker  if not match:
437*6777b538SAndroid Build Coastguard Worker    raise ParseError('Could not find java class in javap output')
438*6777b538SAndroid Build Coastguard Worker  java_class = java_types.JavaClass(match.group(1).replace('.', '/'))
439*6777b538SAndroid Build Coastguard Worker  type_resolver = java_types.TypeResolver(java_class)
440*6777b538SAndroid Build Coastguard Worker
441*6777b538SAndroid Build Coastguard Worker  constant_fields = []
442*6777b538SAndroid Build Coastguard Worker  for match in _JAVAP_FINAL_FIELD_REGEX.finditer(contents):
443*6777b538SAndroid Build Coastguard Worker    name, value = match.groups()
444*6777b538SAndroid Build Coastguard Worker    constant_fields.append(ParsedConstantField(name=name, value=value))
445*6777b538SAndroid Build Coastguard Worker  constant_fields.sort()
446*6777b538SAndroid Build Coastguard Worker
447*6777b538SAndroid Build Coastguard Worker  called_by_natives = []
448*6777b538SAndroid Build Coastguard Worker  for match in _JAVAP_METHOD_REGEX.finditer(contents):
449*6777b538SAndroid Build Coastguard Worker    modifiers, name, descriptor = match.groups()
450*6777b538SAndroid Build Coastguard Worker    if name == java_class.full_name_with_dots:
451*6777b538SAndroid Build Coastguard Worker      name = '<init>'
452*6777b538SAndroid Build Coastguard Worker    signature = java_types.JavaSignature.from_descriptor(descriptor)
453*6777b538SAndroid Build Coastguard Worker
454*6777b538SAndroid Build Coastguard Worker    called_by_natives.append(
455*6777b538SAndroid Build Coastguard Worker        ParsedCalledByNative(java_class=java_class,
456*6777b538SAndroid Build Coastguard Worker                             name=name,
457*6777b538SAndroid Build Coastguard Worker                             signature=signature,
458*6777b538SAndroid Build Coastguard Worker                             static='static' in modifiers))
459*6777b538SAndroid Build Coastguard Worker  called_by_natives.sort()
460*6777b538SAndroid Build Coastguard Worker
461*6777b538SAndroid Build Coastguard Worker  # Although javac will not allow multiple methods with no args and different
462*6777b538SAndroid Build Coastguard Worker  # return types, Class.class has just that, and it breaks with our
463*6777b538SAndroid Build Coastguard Worker  # name-mangling logic which assumes this cannot happen.
464*6777b538SAndroid Build Coastguard Worker  if java_class.full_name_with_slashes == 'java/lang/Class':
465*6777b538SAndroid Build Coastguard Worker    called_by_natives = [
466*6777b538SAndroid Build Coastguard Worker        x for x in called_by_natives if 'TypeDescriptor' not in (
467*6777b538SAndroid Build Coastguard Worker            x.signature.return_type.non_array_full_name_with_slashes)
468*6777b538SAndroid Build Coastguard Worker    ]
469*6777b538SAndroid Build Coastguard Worker
470*6777b538SAndroid Build Coastguard Worker  return ParsedFile(filename=filename,
471*6777b538SAndroid Build Coastguard Worker                    type_resolver=type_resolver,
472*6777b538SAndroid Build Coastguard Worker                    proxy_methods=[],
473*6777b538SAndroid Build Coastguard Worker                    non_proxy_methods=[],
474*6777b538SAndroid Build Coastguard Worker                    called_by_natives=called_by_natives,
475*6777b538SAndroid Build Coastguard Worker                    constant_fields=constant_fields)
476