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