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 (get_error_message, is_java_method, is_bytes,
18                         is_unicode, py2to3)
19
20from .arguments import JavaArgumentParser, PythonArgumentParser
21
22
23def no_dynamic_method(*args):
24    pass
25
26
27@py2to3
28class _DynamicMethod(object):
29    _underscore_name = NotImplemented
30
31    def __init__(self, lib):
32        self.method = self._get_method(lib)
33
34    def _get_method(self, lib):
35        for name in self._underscore_name, self._camelCaseName:
36            method = getattr(lib, name, None)
37            if callable(method):
38                return method
39        return no_dynamic_method
40
41    @property
42    def _camelCaseName(self):
43        tokens = self._underscore_name.split('_')
44        return ''.join([tokens[0]] + [t.capitalize() for t in tokens[1:]])
45
46    @property
47    def name(self):
48        return self.method.__name__
49
50    def __call__(self, *args):
51        try:
52            return self._handle_return_value(self.method(*args))
53        except:
54            raise DataError("Calling dynamic method '%s' failed: %s"
55                            % (self.method.__name__, get_error_message()))
56
57    def _handle_return_value(self, value):
58        raise NotImplementedError
59
60    def _to_string(self, value):
61        if is_unicode(value):
62            return value
63        if is_bytes(value):
64            return value.decode('UTF-8')
65        raise DataError('Return value must be string.')
66
67    def _to_list_of_strings(self, value):
68        try:
69            return [self._to_string(v) for v in value]
70        except (TypeError, DataError):
71            raise DataError('Return value must be list of strings.')
72
73    def __nonzero__(self):
74        return self.method is not no_dynamic_method
75
76
77class GetKeywordNames(_DynamicMethod):
78    _underscore_name = 'get_keyword_names'
79
80    def _handle_return_value(self, value):
81        names = self._to_list_of_strings(value or [])
82        return list(self._remove_duplicates(names))
83
84    def _remove_duplicates(self, names):
85        seen = set()
86        for name in names:
87            if name not in seen:
88                seen.add(name)
89                yield name
90
91
92class RunKeyword(_DynamicMethod):
93    _underscore_name = 'run_keyword'
94
95    @property
96    def supports_kwargs(self):
97        if is_java_method(self.method):
98            return self._supports_java_kwargs(self.method)
99        return self._supports_python_kwargs(self.method)
100
101    def _supports_python_kwargs(self, method):
102        spec = PythonArgumentParser().parse(method)
103        return len(spec.positional) == 3
104
105    def _supports_java_kwargs(self, method):
106        func = self.method.im_func if hasattr(method, 'im_func') else method
107        signatures = func.argslist[:func.nargs]
108        spec = JavaArgumentParser().parse(signatures)
109        return (self._java_single_signature_kwargs(spec) or
110                self._java_multi_signature_kwargs(spec))
111
112    def _java_single_signature_kwargs(self, spec):
113        return len(spec.positional) == 1 and spec.varargs and spec.kwargs
114
115    def _java_multi_signature_kwargs(self, spec):
116        return len(spec.positional) == 3 and not (spec.varargs or spec.kwargs)
117
118
119class GetKeywordDocumentation(_DynamicMethod):
120    _underscore_name = 'get_keyword_documentation'
121
122    def _handle_return_value(self, value):
123        return self._to_string(value or '')
124
125
126class GetKeywordArguments(_DynamicMethod):
127    _underscore_name = 'get_keyword_arguments'
128
129    def __init__(self, lib):
130        _DynamicMethod.__init__(self, lib)
131        self._supports_kwargs = RunKeyword(lib).supports_kwargs
132
133    def _handle_return_value(self, value):
134        if value is None:
135            if self._supports_kwargs:
136                return ['*varargs', '**kwargs']
137            return ['*varargs']
138        return self._to_list_of_strings(value)
139
140
141class GetKeywordTypes(_DynamicMethod):
142    _underscore_name = 'get_keyword_types'
143
144    def _handle_return_value(self, value):
145        return value
146
147
148class GetKeywordTags(_DynamicMethod):
149    _underscore_name = 'get_keyword_tags'
150
151    def _handle_return_value(self, value):
152        return self._to_list_of_strings(value or [])
153