xref: /aosp_15_r20/external/sl4a/Docs/generate_api_from_javadoc.py (revision 456ef56af69dcf0481dd36cc45216c4002d72fa3)
1*456ef56aSSadaf Ebrahimi#!/usr/bin/env python
2*456ef56aSSadaf Ebrahimi#
3*456ef56aSSadaf Ebrahimi#  Copyright (C) 2018 Google, Inc.
4*456ef56aSSadaf Ebrahimi#
5*456ef56aSSadaf Ebrahimi#  Licensed under the Apache License, Version 2.0 (the "License");
6*456ef56aSSadaf Ebrahimi#  you may not use this file except in compliance with the License.
7*456ef56aSSadaf Ebrahimi#  You may obtain a copy of the License at:
8*456ef56aSSadaf Ebrahimi#
9*456ef56aSSadaf Ebrahimi#  http://www.apache.org/licenses/LICENSE-2.0
10*456ef56aSSadaf Ebrahimi#
11*456ef56aSSadaf Ebrahimi#  Unless required by applicable law or agreed to in writing, software
12*456ef56aSSadaf Ebrahimi#  distributed under the License is distributed on an "AS IS" BASIS,
13*456ef56aSSadaf Ebrahimi#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*456ef56aSSadaf Ebrahimi#  See the License for the specific language governing permissions and
15*456ef56aSSadaf Ebrahimi#  limitations under the License.
16*456ef56aSSadaf Ebrahimiimport os
17*456ef56aSSadaf Ebrahimiimport re
18*456ef56aSSadaf Ebrahimi
19*456ef56aSSadaf Ebrahimi# Matches a JavaDoc comment followed by an @Rpc annotation.
20*456ef56aSSadaf Ebrahimiimport subprocess
21*456ef56aSSadaf Ebrahimi
22*456ef56aSSadaf Ebrahimi"""A regex that captures the JavaDoc comment and function signature."""
23*456ef56aSSadaf EbrahimiJAVADOC_RPC_REGEX = re.compile(
24*456ef56aSSadaf Ebrahimi    # Capture the entire comment string.
25*456ef56aSSadaf Ebrahimi    r'(?P<comment>/\*\*(?:(?!/\*\*).)*?\*/)(?:(?:(?!\*/).)*?)\s*'
26*456ef56aSSadaf Ebrahimi    # Find at least one @Rpc Annotation
27*456ef56aSSadaf Ebrahimi    r'(?:@\w+\s*)*?(?:@Rpc.*?\s*)(?:@\w+\s*)*?'
28*456ef56aSSadaf Ebrahimi    # Capture the function signature, ignoring the throws statement
29*456ef56aSSadaf Ebrahimi    # (the throws information will be pulled from the comment).
30*456ef56aSSadaf Ebrahimi    r'(?P<function_signature>.*?)(?:throws.*?)?{',
31*456ef56aSSadaf Ebrahimi    flags=re.MULTILINE | re.DOTALL)
32*456ef56aSSadaf Ebrahimi
33*456ef56aSSadaf Ebrahimi"""
34*456ef56aSSadaf EbrahimiCaptures javadoc "frills" like the ones found below:
35*456ef56aSSadaf Ebrahimi
36*456ef56aSSadaf Ebrahimi/**
37*456ef56aSSadaf Ebrahimi  *
38*456ef56aSSadaf Ebrahimi  */
39*456ef56aSSadaf Ebrahimi
40*456ef56aSSadaf Ebrahimi/** */
41*456ef56aSSadaf Ebrahimi
42*456ef56aSSadaf Ebrahimi"""
43*456ef56aSSadaf EbrahimiCAPTURE_JAVADOC_FRILLS = re.compile(
44*456ef56aSSadaf Ebrahimi    r'(^\s*(/\*\*$|/\*\* |\*($| ))|\s*\*/\s*$)',
45*456ef56aSSadaf Ebrahimi    re.MULTILINE)
46*456ef56aSSadaf Ebrahimi
47*456ef56aSSadaf Ebrahimi"""A regex to capture the individual pieces of the function signature."""
48*456ef56aSSadaf EbrahimiCAPTURE_FUNCTION_SIGNATURE = re.compile(
49*456ef56aSSadaf Ebrahimi    # Capture any non-static function
50*456ef56aSSadaf Ebrahimi    r'(public|private)'
51*456ef56aSSadaf Ebrahimi    # Allow synchronized and @Annotations()
52*456ef56aSSadaf Ebrahimi    r'[( synchronized)(@\w+\(.*?\)?)]*?'
53*456ef56aSSadaf Ebrahimi    # Return Type (Allow n-number of generics and arrays)
54*456ef56aSSadaf Ebrahimi    r'(?P<return_type>\w+(?:[\[\]<>\w ,]*?)?)\s+'
55*456ef56aSSadaf Ebrahimi    # Capture functionName
56*456ef56aSSadaf Ebrahimi    r'(?P<function_name>\w*)\s*'
57*456ef56aSSadaf Ebrahimi    # Capture anything enclosed in parens
58*456ef56aSSadaf Ebrahimi    r'\((?P<parameters>.*)\)',
59*456ef56aSSadaf Ebrahimi    re.MULTILINE | re.DOTALL)
60*456ef56aSSadaf Ebrahimi
61*456ef56aSSadaf Ebrahimi"""Matches a parameter and its RPC annotations."""
62*456ef56aSSadaf EbrahimiCAPTURE_PARAMETER = re.compile(
63*456ef56aSSadaf Ebrahimi    r'(?:'
64*456ef56aSSadaf Ebrahimi    r'(?P<optional>@RpcOptional\s+)?'
65*456ef56aSSadaf Ebrahimi    r'(?P<rpc_param>@RpcParameter\(.*?\)\s*)?'
66*456ef56aSSadaf Ebrahimi    r'(?P<default>@RpcDefault\((?P<default_value>.*)\)\s*)?'
67*456ef56aSSadaf Ebrahimi    r')*'
68*456ef56aSSadaf Ebrahimi    r'(?P<param_type>\w+)\s+(?P<param_name>\w+)',
69*456ef56aSSadaf Ebrahimi    flags=re.MULTILINE | re.DOTALL)
70*456ef56aSSadaf Ebrahimi
71*456ef56aSSadaf Ebrahimi
72*456ef56aSSadaf Ebrahimiclass Facade(object):
73*456ef56aSSadaf Ebrahimi    """A class representing a Facade.
74*456ef56aSSadaf Ebrahimi
75*456ef56aSSadaf Ebrahimi    Attributes:
76*456ef56aSSadaf Ebrahimi        path: the path the facade is located at.
77*456ef56aSSadaf Ebrahimi        directory: the
78*456ef56aSSadaf Ebrahimi    """
79*456ef56aSSadaf Ebrahimi
80*456ef56aSSadaf Ebrahimi    def __init__(self, path):
81*456ef56aSSadaf Ebrahimi        self.path = path
82*456ef56aSSadaf Ebrahimi        self.directory = os.path.dirname(self.path)
83*456ef56aSSadaf Ebrahimi        # -5 removes the '.java' file extension
84*456ef56aSSadaf Ebrahimi        self.name = path[path.rfind('/') + 1:-5]
85*456ef56aSSadaf Ebrahimi        self.rpcs = list()
86*456ef56aSSadaf Ebrahimi
87*456ef56aSSadaf Ebrahimi
88*456ef56aSSadaf Ebrahimidef main():
89*456ef56aSSadaf Ebrahimi    basepath = os.path.abspath(os.path.join(os.path.dirname(
90*456ef56aSSadaf Ebrahimi        os.path.realpath(__file__)), '..'))
91*456ef56aSSadaf Ebrahimi
92*456ef56aSSadaf Ebrahimi    facades = list()
93*456ef56aSSadaf Ebrahimi
94*456ef56aSSadaf Ebrahimi    for path, dirs, files in os.walk(basepath):
95*456ef56aSSadaf Ebrahimi        for file_name in files:
96*456ef56aSSadaf Ebrahimi            if file_name.endswith('Facade.java'):
97*456ef56aSSadaf Ebrahimi                facades.append(parse_facade_file(os.path.join(path, file_name)))
98*456ef56aSSadaf Ebrahimi
99*456ef56aSSadaf Ebrahimi    basepath = os.path.abspath(os.path.join(os.path.dirname(
100*456ef56aSSadaf Ebrahimi        os.path.realpath(__file__)), '..'))
101*456ef56aSSadaf Ebrahimi    write_output(facades, os.path.join(basepath, 'Docs/ApiReference.md'))
102*456ef56aSSadaf Ebrahimi
103*456ef56aSSadaf Ebrahimi
104*456ef56aSSadaf Ebrahimidef write_output(facades, output_path):
105*456ef56aSSadaf Ebrahimi    facades = sorted(facades, key=lambda x: x.directory)
106*456ef56aSSadaf Ebrahimi
107*456ef56aSSadaf Ebrahimi    git_rev = None
108*456ef56aSSadaf Ebrahimi    try:
109*456ef56aSSadaf Ebrahimi        git_rev = subprocess.check_output('git rev-parse HEAD',
110*456ef56aSSadaf Ebrahimi                                          shell=True).decode('utf-8').strip()
111*456ef56aSSadaf Ebrahimi    except subprocess.CalledProcessError as e:
112*456ef56aSSadaf Ebrahimi        # Getting the commit ID is optional; we continue if we cannot get it
113*456ef56aSSadaf Ebrahimi        pass
114*456ef56aSSadaf Ebrahimi
115*456ef56aSSadaf Ebrahimi    with open(output_path, 'w') as fd:
116*456ef56aSSadaf Ebrahimi        if git_rev:
117*456ef56aSSadaf Ebrahimi            fd.write('Generated at commit `%s`\n\n' % git_rev)
118*456ef56aSSadaf Ebrahimi        fd.write('# Facade Groups')
119*456ef56aSSadaf Ebrahimi        prev_directory = ''
120*456ef56aSSadaf Ebrahimi        for facade in facades:
121*456ef56aSSadaf Ebrahimi            if facade.directory != prev_directory:
122*456ef56aSSadaf Ebrahimi                fd.write('\n\n## %s\n\n' % facade.directory[
123*456ef56aSSadaf Ebrahimi                                           facade.directory.rfind('/') + 1:])
124*456ef56aSSadaf Ebrahimi                prev_directory = facade.directory
125*456ef56aSSadaf Ebrahimi            fd.write('  * [%s](#%s)\n' % (facade.name, facade.name.lower()))
126*456ef56aSSadaf Ebrahimi
127*456ef56aSSadaf Ebrahimi        fd.write('\n# Facades\n\n')
128*456ef56aSSadaf Ebrahimi        for facade in facades:
129*456ef56aSSadaf Ebrahimi            fd.write('\n## %s' % facade.name)
130*456ef56aSSadaf Ebrahimi            for rpc in facade.rpcs:
131*456ef56aSSadaf Ebrahimi                fd.write('\n\n### %s\n\n' % rpc.name)
132*456ef56aSSadaf Ebrahimi                fd.write('%s\n' % rpc)
133*456ef56aSSadaf Ebrahimi
134*456ef56aSSadaf Ebrahimi
135*456ef56aSSadaf Ebrahimidef parse_facade_file(file_path):
136*456ef56aSSadaf Ebrahimi    """Parses a .*Facade.java file and represents it as a Facade object"""
137*456ef56aSSadaf Ebrahimi    facade = Facade(file_path)
138*456ef56aSSadaf Ebrahimi    with open(file_path, 'r') as content_file:
139*456ef56aSSadaf Ebrahimi        content = content_file.read()
140*456ef56aSSadaf Ebrahimi        matches = re.findall(JAVADOC_RPC_REGEX, content)
141*456ef56aSSadaf Ebrahimi        for match in matches:
142*456ef56aSSadaf Ebrahimi            rpc_function = DocumentedFunction(
143*456ef56aSSadaf Ebrahimi                match[0].replace('\\n', '\n'),  # match[0]: JavaDoc comment
144*456ef56aSSadaf Ebrahimi                match[1].replace('\\n', '\n'))  # match[1]: function signature
145*456ef56aSSadaf Ebrahimi            facade.rpcs.append(rpc_function)
146*456ef56aSSadaf Ebrahimi    facade.rpcs.sort(key=lambda rpc: rpc.name)
147*456ef56aSSadaf Ebrahimi    return facade
148*456ef56aSSadaf Ebrahimi
149*456ef56aSSadaf Ebrahimi
150*456ef56aSSadaf Ebrahimiclass DefaultValue(object):
151*456ef56aSSadaf Ebrahimi    """An object representation of a default value.
152*456ef56aSSadaf Ebrahimi
153*456ef56aSSadaf Ebrahimi    Functions as Optional in Java, or a pointer in C++.
154*456ef56aSSadaf Ebrahimi
155*456ef56aSSadaf Ebrahimi    Attributes:
156*456ef56aSSadaf Ebrahimi        value: the default value
157*456ef56aSSadaf Ebrahimi    """
158*456ef56aSSadaf Ebrahimi    def __init__(self, default_value=None):
159*456ef56aSSadaf Ebrahimi        self.value = default_value
160*456ef56aSSadaf Ebrahimi
161*456ef56aSSadaf Ebrahimi
162*456ef56aSSadaf Ebrahimiclass DocumentedValue(object):
163*456ef56aSSadaf Ebrahimi    def __init__(self):
164*456ef56aSSadaf Ebrahimi        """Creates an empty DocumentedValue object."""
165*456ef56aSSadaf Ebrahimi        self.type = 'void'
166*456ef56aSSadaf Ebrahimi        self.name = None
167*456ef56aSSadaf Ebrahimi        self.description = None
168*456ef56aSSadaf Ebrahimi        self.default_value = None
169*456ef56aSSadaf Ebrahimi
170*456ef56aSSadaf Ebrahimi    def set_type(self, param_type):
171*456ef56aSSadaf Ebrahimi        self.type = param_type
172*456ef56aSSadaf Ebrahimi        return self
173*456ef56aSSadaf Ebrahimi
174*456ef56aSSadaf Ebrahimi    def set_name(self, name):
175*456ef56aSSadaf Ebrahimi        self.name = name
176*456ef56aSSadaf Ebrahimi        return self
177*456ef56aSSadaf Ebrahimi
178*456ef56aSSadaf Ebrahimi    def set_description(self, description):
179*456ef56aSSadaf Ebrahimi        self.description = description
180*456ef56aSSadaf Ebrahimi        return self
181*456ef56aSSadaf Ebrahimi
182*456ef56aSSadaf Ebrahimi    def set_default_value(self, default_value):
183*456ef56aSSadaf Ebrahimi        self.default_value = default_value
184*456ef56aSSadaf Ebrahimi        return self
185*456ef56aSSadaf Ebrahimi
186*456ef56aSSadaf Ebrahimi    def __str__(self):
187*456ef56aSSadaf Ebrahimi        if self.name is None:
188*456ef56aSSadaf Ebrahimi            return self.description
189*456ef56aSSadaf Ebrahimi        if self.default_value is None:
190*456ef56aSSadaf Ebrahimi            return '%s: %s' % (self.name, self.description)
191*456ef56aSSadaf Ebrahimi        else:
192*456ef56aSSadaf Ebrahimi            return '%s: %s (default: %s)' % (self.name, self.description,
193*456ef56aSSadaf Ebrahimi                                             self.default_value.value)
194*456ef56aSSadaf Ebrahimi
195*456ef56aSSadaf Ebrahimi
196*456ef56aSSadaf Ebrahimiclass DocumentedFunction(object):
197*456ef56aSSadaf Ebrahimi    """A combination of all function documentation into a single object.
198*456ef56aSSadaf Ebrahimi
199*456ef56aSSadaf Ebrahimi    Attributes:
200*456ef56aSSadaf Ebrahimi        _description: A string that describes the function.
201*456ef56aSSadaf Ebrahimi        _parameters: A dictionary of {parameter name: DocumentedValue object}
202*456ef56aSSadaf Ebrahimi        _return: a DocumentedValue with information on the returned value.
203*456ef56aSSadaf Ebrahimi        _throws: A dictionary of {throw type (str): DocumentedValue object}
204*456ef56aSSadaf Ebrahimi
205*456ef56aSSadaf Ebrahimi    """
206*456ef56aSSadaf Ebrahimi    def __init__(self, comment, function_signature):
207*456ef56aSSadaf Ebrahimi        self._name = None
208*456ef56aSSadaf Ebrahimi        self._description = None
209*456ef56aSSadaf Ebrahimi        self._parameters = {}
210*456ef56aSSadaf Ebrahimi        self._return = DocumentedValue()
211*456ef56aSSadaf Ebrahimi        self._throws = {}
212*456ef56aSSadaf Ebrahimi
213*456ef56aSSadaf Ebrahimi        self._parse_comment(comment)
214*456ef56aSSadaf Ebrahimi        self._parse_function_signature(function_signature)
215*456ef56aSSadaf Ebrahimi
216*456ef56aSSadaf Ebrahimi    @property
217*456ef56aSSadaf Ebrahimi    def name(self):
218*456ef56aSSadaf Ebrahimi        return self._name
219*456ef56aSSadaf Ebrahimi
220*456ef56aSSadaf Ebrahimi    def _parse_comment(self, comment):
221*456ef56aSSadaf Ebrahimi        """Parses a JavaDoc comment into DocumentedFunction attributes."""
222*456ef56aSSadaf Ebrahimi        comment = str(re.sub(CAPTURE_JAVADOC_FRILLS, '', comment))
223*456ef56aSSadaf Ebrahimi        tag = 'description'
224*456ef56aSSadaf Ebrahimi        tag_data = ''
225*456ef56aSSadaf Ebrahimi        for line in comment.split('\n'):
226*456ef56aSSadaf Ebrahimi            line.strip()
227*456ef56aSSadaf Ebrahimi            if line.startswith('@'):
228*456ef56aSSadaf Ebrahimi                self._finalize_tag(tag, tag_data)
229*456ef56aSSadaf Ebrahimi                tag_end_index = line.find(' ')
230*456ef56aSSadaf Ebrahimi                tag = line[1:tag_end_index]
231*456ef56aSSadaf Ebrahimi                tag_data = line[tag_end_index + 1:]
232*456ef56aSSadaf Ebrahimi            else:
233*456ef56aSSadaf Ebrahimi                if not tag_data:
234*456ef56aSSadaf Ebrahimi                    whitespace_char = ''
235*456ef56aSSadaf Ebrahimi                elif (line.startswith(' ')
236*456ef56aSSadaf Ebrahimi                      or tag_data.endswith('\n')
237*456ef56aSSadaf Ebrahimi                      or line == ''):
238*456ef56aSSadaf Ebrahimi                    whitespace_char = '\n'
239*456ef56aSSadaf Ebrahimi                else:
240*456ef56aSSadaf Ebrahimi                    whitespace_char = ' '
241*456ef56aSSadaf Ebrahimi                tag_data = '%s%s%s' % (tag_data, whitespace_char, line)
242*456ef56aSSadaf Ebrahimi        self._finalize_tag(tag, tag_data.strip())
243*456ef56aSSadaf Ebrahimi
244*456ef56aSSadaf Ebrahimi    def __str__(self):
245*456ef56aSSadaf Ebrahimi        params_signature = ', '.join(['%s %s' % (param.type, param.name)
246*456ef56aSSadaf Ebrahimi                                      for param in self._parameters.values()])
247*456ef56aSSadaf Ebrahimi        params_description = '\n    '.join(['%s: %s' % (param.name,
248*456ef56aSSadaf Ebrahimi                                                        param.description)
249*456ef56aSSadaf Ebrahimi                                            for param in
250*456ef56aSSadaf Ebrahimi                                            self._parameters.values()])
251*456ef56aSSadaf Ebrahimi        if params_description:
252*456ef56aSSadaf Ebrahimi            params_description = ('\n**Parameters:**\n\n    %s\n' %
253*456ef56aSSadaf Ebrahimi                                  params_description)
254*456ef56aSSadaf Ebrahimi        return_description = '\n' if self._return else ''
255*456ef56aSSadaf Ebrahimi        if self._return:
256*456ef56aSSadaf Ebrahimi            return_description += ('**Returns:**\n\n    %s' %
257*456ef56aSSadaf Ebrahimi                                   self._return.description)
258*456ef56aSSadaf Ebrahimi        return (
259*456ef56aSSadaf Ebrahimi            # ReturnType functionName(Parameters)
260*456ef56aSSadaf Ebrahimi            '%s %s(%s)\n\n'
261*456ef56aSSadaf Ebrahimi            # Description
262*456ef56aSSadaf Ebrahimi            '%s\n'
263*456ef56aSSadaf Ebrahimi            # Params & Return
264*456ef56aSSadaf Ebrahimi            '%s%s' % (self._return.type, self._name,
265*456ef56aSSadaf Ebrahimi                      params_signature, self._description,
266*456ef56aSSadaf Ebrahimi                      params_description, return_description)).strip()
267*456ef56aSSadaf Ebrahimi
268*456ef56aSSadaf Ebrahimi    def _parse_function_signature(self, function_signature):
269*456ef56aSSadaf Ebrahimi        """Parses the function signature into DocumentedFunction attributes."""
270*456ef56aSSadaf Ebrahimi        header_match = re.search(CAPTURE_FUNCTION_SIGNATURE, function_signature)
271*456ef56aSSadaf Ebrahimi        self._name = header_match.group('function_name')
272*456ef56aSSadaf Ebrahimi        self._return.set_type(header_match.group('return_type'))
273*456ef56aSSadaf Ebrahimi
274*456ef56aSSadaf Ebrahimi        for match in re.finditer(CAPTURE_PARAMETER,
275*456ef56aSSadaf Ebrahimi                                 header_match.group('parameters')):
276*456ef56aSSadaf Ebrahimi            param_name = match.group('param_name')
277*456ef56aSSadaf Ebrahimi            param_type = match.group('param_type')
278*456ef56aSSadaf Ebrahimi            if match.group('default_value'):
279*456ef56aSSadaf Ebrahimi                default = DefaultValue(match.group('default_value'))
280*456ef56aSSadaf Ebrahimi            elif match.group('optional'):
281*456ef56aSSadaf Ebrahimi                default = DefaultValue(None)
282*456ef56aSSadaf Ebrahimi            else:
283*456ef56aSSadaf Ebrahimi                default = None
284*456ef56aSSadaf Ebrahimi
285*456ef56aSSadaf Ebrahimi            if param_name in self._parameters:
286*456ef56aSSadaf Ebrahimi                param = self._parameters[param_name]
287*456ef56aSSadaf Ebrahimi            else:
288*456ef56aSSadaf Ebrahimi                param = DocumentedValue()
289*456ef56aSSadaf Ebrahimi            param.set_type(param_type)
290*456ef56aSSadaf Ebrahimi            param.set_name(param_name)
291*456ef56aSSadaf Ebrahimi            param.set_default_value(default)
292*456ef56aSSadaf Ebrahimi
293*456ef56aSSadaf Ebrahimi    def _finalize_tag(self, tag, tag_data):
294*456ef56aSSadaf Ebrahimi        """Finalize the JavaDoc @tag by adding it to the correct field."""
295*456ef56aSSadaf Ebrahimi        name = tag_data[:tag_data.find(' ')]
296*456ef56aSSadaf Ebrahimi        description = tag_data[tag_data.find(' ') + 1:].strip()
297*456ef56aSSadaf Ebrahimi        if tag == 'description':
298*456ef56aSSadaf Ebrahimi            self._description = tag_data
299*456ef56aSSadaf Ebrahimi        elif tag == 'param':
300*456ef56aSSadaf Ebrahimi            if name in self._parameters:
301*456ef56aSSadaf Ebrahimi                param = self._parameters[name]
302*456ef56aSSadaf Ebrahimi            else:
303*456ef56aSSadaf Ebrahimi                param = DocumentedValue().set_name(name)
304*456ef56aSSadaf Ebrahimi                self._parameters[name] = param
305*456ef56aSSadaf Ebrahimi            param.set_description(description)
306*456ef56aSSadaf Ebrahimi        elif tag == 'return':
307*456ef56aSSadaf Ebrahimi            self._return.set_description(tag_data)
308*456ef56aSSadaf Ebrahimi        elif tag == 'throws':
309*456ef56aSSadaf Ebrahimi            new_throws = DocumentedValue().set_name(name)
310*456ef56aSSadaf Ebrahimi            new_throws.set_description(description)
311*456ef56aSSadaf Ebrahimi            self._throws[name] = new_throws
312*456ef56aSSadaf Ebrahimi
313*456ef56aSSadaf Ebrahimi
314*456ef56aSSadaf Ebrahimiif __name__ == '__main__':
315*456ef56aSSadaf Ebrahimi    main()
316