1#  Copyright 2008-2015 Nokia Networks
2#  Copyright 2016-     Robot Framework Foundation
3#
4#  Licensed under the Apache License, Version 2.0 (the "License");
5#  you may not use this file except in compliance with the License.
6#  You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10#  Unless required by applicable law or agreed to in writing, software
11#  distributed under the License is distributed on an "AS IS" BASIS,
12#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13#  See the License for the specific language governing permissions and
14#  limitations under the License.
15
16from robot.errors import DataError
17from robot.utils import JYTHON, PY_VERSION, PY2
18from robot.variables import is_dict_var, is_list_var, is_scalar_var
19
20from .argumentspec import ArgumentSpec
21
22
23if PY2:
24    from inspect import getargspec, ismethod
25
26    def getfullargspec(func):
27        return getargspec(func) + ([], None, {})
28else:
29    from inspect import getfullargspec, ismethod
30
31if PY_VERSION >= (3, 5):
32    import typing
33else:
34    typing = None
35
36if JYTHON:
37    from java.lang import Class
38    from java.util import List, Map
39
40
41class _ArgumentParser(object):
42
43    def __init__(self, type='Keyword'):
44        self._type = type
45
46    def parse(self, source, name=None):
47        raise NotImplementedError
48
49
50class PythonArgumentParser(_ArgumentParser):
51
52    def parse(self, handler, name=None):
53        args, varargs, kwargs, defaults, kwonly, kwonlydefaults, annotations \
54                = getfullargspec(handler)
55        if ismethod(handler) or handler.__name__ == '__init__':
56            args = args[1:]  # drop 'self'
57        spec = ArgumentSpec(
58            name,
59            self._type,
60            positional=args,
61            varargs=varargs,
62            kwargs=kwargs,
63            kwonlyargs=kwonly,
64            defaults=self._get_defaults(args, defaults, kwonlydefaults)
65        )
66        spec.types = self._get_types(handler, annotations, spec)
67        return spec
68
69    def _get_defaults(self, args, default_values, kwonlydefaults):
70        if default_values:
71            defaults = dict(zip(args[-len(default_values):], default_values))
72        else:
73            defaults = {}
74        if kwonlydefaults:
75            defaults.update(kwonlydefaults)
76        return defaults
77
78    def _get_types(self, handler, annotations, spec):
79        types = getattr(handler, 'robot_types', ())
80        if types is None:
81            return None
82        if types:
83            return types
84        return self._get_type_hints(handler, annotations, spec)
85
86    def _get_type_hints(self, handler, annotations, spec):
87        if not typing:
88            return annotations
89        try:
90            type_hints = typing.get_type_hints(handler)
91        except Exception:  # Can raise pretty much anything
92            return annotations
93        self._remove_mismatching_type_hints(type_hints, spec.argument_names)
94        self._remove_optional_none_type_hints(type_hints, spec.defaults)
95        return type_hints
96
97    def _remove_mismatching_type_hints(self, type_hints, argument_names):
98        # typing.get_type_hints returns info from the original function even
99        # if it is decorated. Argument names are got from the wrapping
100        # decorator and thus there is a mismatch that needs to be resolved.
101        mismatch = set(type_hints) - set(argument_names)
102        for name in mismatch:
103            type_hints.pop(name)
104
105    def _remove_optional_none_type_hints(self, type_hints, defaults):
106        # If argument has None as a default, typing.get_type_hints adds
107        # optional None to the information it returns. We don't want that.
108        for arg in defaults:
109            if defaults[arg] is None and arg in type_hints:
110                type_ = type_hints[arg]
111                if self._is_union(type_):
112                    types = type_.__args__
113                    if len(types) == 2 and types[1] is type(None):
114                        type_hints[arg] = types[0]
115
116    def _is_union(self, type_):
117        if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'):
118            type_ = type_.__origin__
119        return isinstance(type_, type(typing.Union))
120
121
122class JavaArgumentParser(_ArgumentParser):
123
124    def parse(self, signatures, name=None):
125        if not signatures:
126            return self._no_signatures_arg_spec(name)
127        elif len(signatures) == 1:
128            return self._single_signature_arg_spec(signatures[0], name)
129        else:
130            return self._multi_signature_arg_spec(signatures, name)
131
132    def _no_signatures_arg_spec(self, name):
133        # Happens when a class has no public constructors
134        return self._format_arg_spec(name)
135
136    def _single_signature_arg_spec(self, signature, name):
137        varargs, kwargs = self._get_varargs_and_kwargs_support(signature.args)
138        positional = len(signature.args) - int(varargs) - int(kwargs)
139        return self._format_arg_spec(name, positional, varargs=varargs,
140                                     kwargs=kwargs)
141
142    def _get_varargs_and_kwargs_support(self, args):
143        if not args:
144            return False, False
145        if self._is_varargs_type(args[-1]):
146            return True, False
147        if not self._is_kwargs_type(args[-1]):
148            return False, False
149        if len(args) > 1 and self._is_varargs_type(args[-2]):
150            return True, True
151        return False, True
152
153    def _is_varargs_type(self, arg):
154        return arg is List or isinstance(arg, Class) and arg.isArray()
155
156    def _is_kwargs_type(self, arg):
157        return arg is Map
158
159    def _multi_signature_arg_spec(self, signatures, name):
160        mina = maxa = len(signatures[0].args)
161        for sig in signatures[1:]:
162            argc = len(sig.args)
163            mina = min(argc, mina)
164            maxa = max(argc, maxa)
165        return self._format_arg_spec(name, maxa, maxa-mina)
166
167    def _format_arg_spec(self, name, positional=0, defaults=0, varargs=False,
168                         kwargs=False):
169        positional = ['arg%d' % (i+1) for i in range(positional)]
170        if defaults:
171            defaults = {name: '' for name in positional[-defaults:]}
172        else:
173            defaults = {}
174        return ArgumentSpec(name, self._type,
175                            positional=positional,
176                            varargs='varargs' if varargs else None,
177                            kwargs='kwargs' if kwargs else None,
178                            defaults=defaults,
179                            supports_named=False)
180
181
182class _ArgumentSpecParser(_ArgumentParser):
183
184    def parse(self, argspec, name=None):
185        spec = ArgumentSpec(name, self._type)
186        kw_only_args = False
187        for arg in argspec:
188            if spec.kwargs:
189                self._raise_invalid_spec('Only last argument can be kwargs.')
190            elif self._is_kwargs(arg):
191                self._add_kwargs(spec, arg)
192            elif self._is_kw_only_separator(arg):
193                if spec.varargs or kw_only_args:
194                    self._raise_invalid_spec('Cannot have multiple varargs.')
195                kw_only_args = True
196            elif self._is_varargs(arg):
197                if spec.varargs or kw_only_args:
198                    self._raise_invalid_spec('Cannot have multiple varargs.')
199                self._add_varargs(spec, arg)
200                kw_only_args = True
201            elif '=' in arg:
202                self._add_arg_with_default(spec, arg, kw_only_args)
203            elif spec.defaults and not kw_only_args:
204                self._raise_invalid_spec('Non-default argument after default '
205                                         'arguments.')
206            else:
207                self._add_arg(spec, arg, kw_only_args)
208        return spec
209
210    def _raise_invalid_spec(self, error):
211        raise DataError('Invalid argument specification: %s' % error)
212
213    def _is_kwargs(self, arg):
214        raise NotImplementedError
215
216    def _add_kwargs(self, spec, kwargs):
217        spec.kwargs = self._format_kwargs(kwargs)
218
219    def _format_kwargs(self, kwargs):
220        raise NotImplementedError
221
222    def _is_kw_only_separator(self, arg):
223        raise NotImplementedError
224
225    def _is_varargs(self, arg):
226        raise NotImplementedError
227
228    def _add_varargs(self, spec, varargs):
229        spec.varargs = self._format_varargs(varargs)
230
231    def _format_varargs(self, varargs):
232        raise NotImplementedError
233
234    def _add_arg_with_default(self, spec, arg, kw_only_arg=False):
235        arg, default = arg.split('=', 1)
236        arg = self._add_arg(spec, arg, kw_only_arg)
237        spec.defaults[arg] = default
238
239    def _format_arg(self, arg):
240        return arg
241
242    def _add_arg(self, spec, arg, kw_only_arg=False):
243        arg = self._format_arg(arg)
244        target = spec.positional if not kw_only_arg else spec.kwonlyargs
245        target.append(arg)
246        return arg
247
248
249class DynamicArgumentParser(_ArgumentSpecParser):
250
251    def _is_kwargs(self, arg):
252        return arg.startswith('**')
253
254    def _format_kwargs(self, kwargs):
255        return kwargs[2:]
256
257    def _is_kw_only_separator(self, arg):
258        return arg == '*'
259
260    def _is_varargs(self, arg):
261        return arg.startswith('*') and not self._is_kwargs(arg)
262
263    def _format_varargs(self, varargs):
264        return varargs[1:]
265
266
267class UserKeywordArgumentParser(_ArgumentSpecParser):
268
269    def _is_kwargs(self, arg):
270        return is_dict_var(arg)
271
272    def _format_kwargs(self, kwargs):
273        return kwargs[2:-1]
274
275    def _is_varargs(self, arg):
276        return is_list_var(arg)
277
278    def _format_varargs(self, varargs):
279        return varargs[2:-1]
280
281    def _is_kw_only_separator(self, arg):
282        return arg == '@{}'
283
284    def _format_arg(self, arg):
285        if not is_scalar_var(arg):
286            self._raise_invalid_spec("Invalid argument syntax '%s'." % arg)
287        return arg[2:-1]
288