1 /**
2 * (C) 2016 - 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
3 * (C) 2016 - 2019 Stanislav Angelovic <angelovic.s@gmail.com>
4 *
5 * @file ProxyGenerator.cpp
6 *
7 * Created on: Feb 1, 2017
8 * Project: sdbus-c++
9 * Description: High-level D-Bus IPC C++ library based on sd-bus
10 *
11 * This file is part of sdbus-c++.
12 *
13 * sdbus-c++ is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation, either version 2.1 of the License, or
16 * (at your option) any later version.
17 *
18 * sdbus-c++ is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
25 */
26
27 // Own
28 #include "generator_utils.h"
29 #include "ProxyGenerator.h"
30
31 // STL
32 #include <iostream>
33 #include <cstdlib>
34 #include <algorithm>
35 #include <iterator>
36 #include <regex>
37
38 using std::endl;
39
40 using sdbuscpp::xml::Document;
41 using sdbuscpp::xml::Node;
42 using sdbuscpp::xml::Nodes;
43
44 /**
45 * Generate proxy code - client glue
46 */
transformXmlToFileImpl(const Document & doc,const char * filename) const47 int ProxyGenerator::transformXmlToFileImpl(const Document &doc, const char *filename) const
48 {
49 Node &root = *(doc.root);
50 Nodes interfaces = root["interface"];
51
52 std::ostringstream code;
53 code << createHeader(filename, StubType::PROXY);
54
55 for (const auto& interface : interfaces)
56 {
57 code << processInterface(*interface);
58 }
59
60 code << "#endif" << endl;
61
62 return writeToFile(filename, code.str());
63 }
64
65
processInterface(Node & interface) const66 std::string ProxyGenerator::processInterface(Node& interface) const
67 {
68 std::string ifaceName = interface.get("name");
69 std::cout << "Generating proxy code for interface " << ifaceName << endl;
70
71 unsigned int namespacesCount = 0;
72 std::string namespacesStr;
73 std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName);
74
75 std::ostringstream body;
76 body << namespacesStr;
77
78 std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1)
79 + "_proxy";
80
81 body << "class " << className << endl
82 << "{" << endl
83 << "public:" << endl
84 << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl
85 << "protected:" << endl
86 << tab << className << "(sdbus::IProxy& proxy)" << endl
87 << tab << tab << ": proxy_(proxy)" << endl;
88
89 Nodes methods = interface["method"];
90 Nodes signals = interface["signal"];
91 Nodes properties = interface["property"];
92
93 std::string registration, declaration;
94 std::tie(registration, declaration) = processSignals(signals);
95
96 body << tab << "{" << endl
97 << registration
98 << tab << "}" << endl << endl;
99
100 body << tab << "~" << className << "() = default;" << endl << endl;
101
102 if (!declaration.empty())
103 body << declaration << endl;
104
105 std::string methodDefinitions, asyncDeclarations;
106 std::tie(methodDefinitions, asyncDeclarations) = processMethods(methods);
107
108 if (!asyncDeclarations.empty())
109 {
110 body << asyncDeclarations << endl;
111 }
112
113 if (!methodDefinitions.empty())
114 {
115 body << "public:" << endl << methodDefinitions;
116 }
117
118 std::string propertyDefinitions = processProperties(properties);
119 if (!propertyDefinitions.empty())
120 {
121 body << "public:" << endl << propertyDefinitions;
122 }
123
124 body << "private:" << endl
125 << tab << "sdbus::IProxy& proxy_;" << endl
126 << "};" << endl << endl
127 << std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
128
129 return body.str();
130 }
131
processMethods(const Nodes & methods) const132 std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes& methods) const
133 {
134 const std::regex patternTimeout{R"(^(\d+)(min|s|ms|us)?$)"};
135
136 std::ostringstream definitionSS, asyncDeclarationSS;
137
138 for (const auto& method : methods)
139 {
140 auto name = method->get("name");
141 auto nameSafe = mangle_name(name);
142 Nodes args = (*method)["arg"];
143 Nodes inArgs = args.select("direction" , "in");
144 Nodes outArgs = args.select("direction" , "out");
145
146 bool dontExpectReply{false};
147 bool async{false};
148 std::string timeoutValue;
149 std::smatch smTimeout;
150
151 Nodes annotations = (*method)["annotation"];
152 for (const auto& annotation : annotations)
153 {
154 if (annotation->get("name") == "org.freedesktop.DBus.Method.NoReply" && annotation->get("value") == "true")
155 dontExpectReply = true;
156 else if (annotation->get("name") == "org.freedesktop.DBus.Method.Async"
157 && (annotation->get("value") == "client" || annotation->get("value") == "clientserver"))
158 async = true;
159 if (annotation->get("name") == "org.freedesktop.DBus.Method.Timeout")
160 timeoutValue = annotation->get("value");
161 }
162 if (dontExpectReply && outArgs.size() > 0)
163 {
164 std::cerr << "Function: " << name << ": ";
165 std::cerr << "Option 'org.freedesktop.DBus.Method.NoReply' not allowed for methods with 'out' variables! Option ignored..." << std::endl;
166 dontExpectReply = false;
167 }
168 if (!timeoutValue.empty() && dontExpectReply)
169 {
170 std::cerr << "Function: " << name << ": ";
171 std::cerr << "Option 'org.freedesktop.DBus.Method.Timeout' not allowed for 'NoReply' methods! Option ignored..." << std::endl;
172 timeoutValue.clear();
173 }
174
175 if (!timeoutValue.empty() && !std::regex_match(timeoutValue, smTimeout, patternTimeout))
176 {
177 std::cerr << "Function: " << name << ": ";
178 std::cerr << "Option 'org.freedesktop.DBus.Method.Timeout' has unsupported timeout value! Option ignored..." << std::endl;
179 timeoutValue.clear();
180 }
181
182 auto retType = outArgsToType(outArgs);
183 std::string inArgStr, inArgTypeStr;
184 std::tie(inArgStr, inArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(inArgs);
185 std::string outArgStr, outArgTypeStr;
186 std::tie(outArgStr, outArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(outArgs);
187
188 const std::string realRetType = (async && !dontExpectReply ? "sdbus::PendingAsyncCall" : async ? "void" : retType);
189 definitionSS << tab << realRetType << " " << nameSafe << "(" << inArgTypeStr << ")" << endl
190 << tab << "{" << endl;
191
192 if (!timeoutValue.empty())
193 {
194 definitionSS << tab << tab << "using namespace std::chrono_literals;" << endl;
195 }
196
197 if (outArgs.size() > 0 && !async)
198 {
199 definitionSS << tab << tab << retType << " result;" << endl;
200 }
201
202 definitionSS << tab << tab << (async && !dontExpectReply ? "return " : "")
203 << "proxy_.callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)";
204
205 if (!timeoutValue.empty())
206 {
207 const auto val = smTimeout.str(1);
208 const auto unit = smTimeout.str(2);
209 definitionSS << ".withTimeout(" << val << (unit.empty() ? "us" : unit) << ")";
210 }
211
212 if (inArgs.size() > 0)
213 {
214 definitionSS << ".withArguments(" << inArgStr << ")";
215 }
216
217 if (async && !dontExpectReply)
218 {
219 auto nameBigFirst = name;
220 nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
221
222 definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")"
223 "{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })";
224
225 asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply("
226 << outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl;
227 }
228 else if (outArgs.size() > 0)
229 {
230 definitionSS << ".storeResultsTo(result);" << endl
231 << tab << tab << "return result";
232 }
233 else if (dontExpectReply)
234 {
235 definitionSS << ".dontExpectReply()";
236 }
237
238 definitionSS << ";" << endl << tab << "}" << endl << endl;
239 }
240
241 return std::make_tuple(definitionSS.str(), asyncDeclarationSS.str());
242 }
243
processSignals(const Nodes & signals) const244 std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes& signals) const
245 {
246 std::ostringstream registrationSS, declarationSS;
247
248 for (const auto& signal : signals)
249 {
250 auto name = signal->get("name");
251 Nodes args = (*signal)["arg"];
252
253 auto nameBigFirst = name;
254 nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
255
256 std::string argStr, argTypeStr;
257 std::tie(argStr, argTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(args);
258
259 registrationSS << tab << tab << "proxy_"
260 ".uponSignal(\"" << name << "\")"
261 ".onInterface(INTERFACE_NAME)"
262 ".call([this](" << argTypeStr << ")"
263 "{ this->on" << nameBigFirst << "(" << argStr << "); });" << endl;
264
265 declarationSS << tab << "virtual void on" << nameBigFirst << "(" << argTypeStr << ") = 0;" << endl;
266 }
267
268 return std::make_tuple(registrationSS.str(), declarationSS.str());
269 }
270
processProperties(const Nodes & properties) const271 std::string ProxyGenerator::processProperties(const Nodes& properties) const
272 {
273 std::ostringstream propertySS;
274 for (const auto& property : properties)
275 {
276 auto propertyName = property->get("name");
277 auto propertyNameSafe = mangle_name(propertyName);
278 auto propertyAccess = property->get("access");
279 auto propertySignature = property->get("type");
280
281 auto propertyType = signature_to_type(propertySignature);
282 auto propertyArg = std::string("value");
283 auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
284
285 if (propertyAccess == "read" || propertyAccess == "readwrite")
286 {
287 propertySS << tab << propertyType << " " << propertyNameSafe << "()" << endl
288 << tab << "{" << endl;
289 propertySS << tab << tab << "return proxy_.getProperty(\"" << propertyName << "\")"
290 ".onInterface(INTERFACE_NAME)";
291 propertySS << ";" << endl << tab << "}" << endl << endl;
292 }
293
294 if (propertyAccess == "readwrite" || propertyAccess == "write")
295 {
296 propertySS << tab << "void " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
297 << tab << "{" << endl;
298 propertySS << tab << tab << "proxy_.setProperty(\"" << propertyName << "\")"
299 ".onInterface(INTERFACE_NAME)"
300 ".toValue(" << propertyArg << ")";
301 propertySS << ";" << endl << tab << "}" << endl << endl;
302 }
303 }
304
305 return propertySS.str();
306 }
307