1 /**
2  * (C) 2016 - 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
3  * (C) 2016 - 2019 Stanislav Angelovic <angelovic.s@gmail.com>
4  *
5  * @file AdaptorGenerator.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 "AdaptorGenerator.h"
30 
31 // STL
32 #include <iostream>
33 #include <fstream>
34 #include <cstdlib>
35 #include <algorithm>
36 #include <iterator>
37 #include <cctype>
38 
39 using std::endl;
40 
41 using sdbuscpp::xml::Document;
42 using sdbuscpp::xml::Node;
43 using sdbuscpp::xml::Nodes;
44 
45 /**
46  * Generate adaptor code - server glue
47  */
transformXmlToFileImpl(const Document & doc,const char * filename) const48 int AdaptorGenerator::transformXmlToFileImpl(const Document& doc, const char* filename) const
49 {
50     Node &root = *(doc.root);
51     Nodes interfaces = root["interface"];
52 
53     std::ostringstream code;
54     code << createHeader(filename, StubType::ADAPTOR);
55 
56     for (const auto& interface : interfaces)
57     {
58         code << processInterface(*interface);
59     }
60 
61     code << "#endif" << endl;
62 
63     return writeToFile(filename, code.str());
64 }
65 
66 
processInterface(Node & interface) const67 std::string AdaptorGenerator::processInterface(Node& interface) const
68 {
69     std::string ifaceName = interface.get("name");
70     std::cout << "Generating adaptor code for interface " << ifaceName << endl;
71 
72     unsigned int namespacesCount = 0;
73     std::string namespacesStr;
74     std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName);
75 
76     std::ostringstream body;
77     body << namespacesStr;
78 
79     std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1)
80             + "_adaptor";
81 
82     body << "class " << className << endl
83             << "{" << endl
84             << "public:" << endl
85             << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl
86             << "protected:" << endl
87             << tab << className << "(sdbus::IObject& object)" << endl
88             << tab << tab << ": object_(object)" << endl;
89 
90     Nodes methods = interface["method"];
91     Nodes signals = interface["signal"];
92     Nodes properties = interface["property"];
93 
94     auto annotations = getAnnotations(interface);
95     std::string annotationRegistration;
96     for (const auto& annotation : annotations)
97     {
98         const auto& annotationName = annotation.first;
99         const auto& annotationValue = annotation.second;
100 
101         if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true")
102             annotationRegistration += ".markAsDeprecated()";
103         else if (annotationName == "org.freedesktop.systemd1.Privileged" && annotationValue == "true")
104             annotationRegistration += ".markAsPrivileged()";
105         else if (annotationName == "org.freedesktop.DBus.Property.EmitsChangedSignal")
106             annotationRegistration += ".withPropertyUpdateBehavior(" + propertyAnnotationToFlag(annotationValue) + ")";
107         else
108             std::cerr << "Node: " << ifaceName << ": "
109                       << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
110     }
111     if(!annotationRegistration.empty())
112     {
113         std::stringstream str;
114         str << tab << tab << "object_.setInterfaceFlags(INTERFACE_NAME)" << annotationRegistration << ";" << endl;
115         annotationRegistration = str.str();
116     }
117 
118     std::string methodRegistration, methodDeclaration;
119     std::tie(methodRegistration, methodDeclaration) = processMethods(methods);
120 
121     std::string signalRegistration, signalMethods;
122     std::tie(signalRegistration, signalMethods) = processSignals(signals);
123 
124     std::string propertyRegistration, propertyAccessorDeclaration;
125     std::tie(propertyRegistration, propertyAccessorDeclaration) = processProperties(properties);
126 
127     body << tab << "{" << endl
128                        << annotationRegistration
129                        << methodRegistration
130                        << signalRegistration
131                        << propertyRegistration
132          << tab << "}" << endl << endl;
133 
134     body << tab << "~" << className << "() = default;" << endl << endl;
135 
136     if (!signalMethods.empty())
137     {
138         body << "public:" << endl << signalMethods;
139     }
140 
141     if (!methodDeclaration.empty())
142     {
143         body << "private:" << endl << methodDeclaration << endl;
144     }
145 
146     if (!propertyAccessorDeclaration.empty())
147     {
148         body << "private:" << endl << propertyAccessorDeclaration << endl;
149     }
150 
151     body << "private:" << endl
152             << tab << "sdbus::IObject& object_;" << endl
153             << "};" << endl << endl
154             << std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
155 
156     return body.str();
157 }
158 
159 
processMethods(const Nodes & methods) const160 std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Nodes& methods) const
161 {
162     std::ostringstream registrationSS, declarationSS;
163 
164     for (const auto& method : methods)
165     {
166         auto methodName = method->get("name");
167         auto methodNameSafe = mangle_name(methodName);
168 
169         auto annotations = getAnnotations(*method);
170         bool async{false};
171         std::string annotationRegistration;
172         for (const auto& annotation : annotations)
173         {
174             const auto& annotationName = annotation.first;
175             const auto& annotationValue = annotation.second;
176 
177             if (annotationName == "org.freedesktop.DBus.Deprecated")
178             {
179                 if (annotationValue == "true")
180                     annotationRegistration += ".markAsDeprecated()";
181             }
182             else if (annotationName == "org.freedesktop.DBus.Method.NoReply")
183             {
184                 if (annotationValue == "true")
185                     annotationRegistration += ".withNoReply()";
186             }
187             else if (annotationName == "org.freedesktop.DBus.Method.Async")
188             {
189                 if (annotationValue == "server" || annotationValue == "clientserver")
190                     async = true;
191             }
192             else if (annotationName == "org.freedesktop.systemd1.Privileged")
193             {
194                 if (annotationValue == "true")
195                     annotationRegistration += ".markAsPrivileged()";
196             }
197             else if (annotationName != "org.freedesktop.DBus.Method.Timeout") // Whatever else...
198             {
199                 std::cerr << "Node: " << methodName << ": "
200                           << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
201             }
202         }
203 
204         Nodes args = (*method)["arg"];
205         Nodes inArgs = args.select("direction" , "in");
206         Nodes outArgs = args.select("direction" , "out");
207 
208         std::string argStr, argTypeStr, argStringsStr, outArgStringsStr;
209         std::tie(argStr, argTypeStr, std::ignore, argStringsStr) = argsToNamesAndTypes(inArgs, async);
210         std::tie(std::ignore, std::ignore, std::ignore, outArgStringsStr) = argsToNamesAndTypes(outArgs);
211 
212         using namespace std::string_literals;
213 
214         registrationSS << tab << tab << "object_.registerMethod(\""
215                 << methodName << "\")"
216                 << ".onInterface(INTERFACE_NAME)"
217                 << (!argStringsStr.empty() ? (".withInputParamNames(" + argStringsStr + ")") : "")
218                 << (!outArgStringsStr.empty() ? (".withOutputParamNames(" + outArgStringsStr + ")") : "")
219                 << ".implementedAs("
220                 << "[this]("
221                 << (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + ">&& result" + (argTypeStr.empty() ? "" : ", ") : "")
222                 << argTypeStr
223                 << "){ " << (async ? "" : "return ") << "this->" << methodNameSafe << "("
224                 << (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "")
225                 << argStr << "); })"
226                 << annotationRegistration << ";" << endl;
227 
228         declarationSS << tab
229                 << "virtual "
230                 << (async ? "void" : outArgsToType(outArgs))
231                 << " " << methodNameSafe
232                 << "("
233                 << (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + ">&& result" + (argTypeStr.empty() ? "" : ", ") : "")
234                 << argTypeStr
235                 << ") = 0;" << endl;
236     }
237 
238     return std::make_tuple(registrationSS.str(), declarationSS.str());
239 }
240 
241 
processSignals(const Nodes & signals) const242 std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Nodes& signals) const
243 {
244     std::ostringstream signalRegistrationSS, signalMethodSS;
245 
246     for (const auto& signal : signals)
247     {
248         auto name = signal->get("name");
249 
250         auto annotations = getAnnotations(*signal);
251         std::string annotationRegistration;
252         for (const auto& annotation : annotations)
253         {
254             const auto& annotationName = annotation.first;
255             const auto& annotationValue = annotation.second;
256 
257             if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true")
258                 annotationRegistration += ".markAsDeprecated()";
259             else
260                 std::cerr << "Node: " << name << ": "
261                           << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
262         }
263 
264         Nodes args = (*signal)["arg"];
265 
266         std::string argStr, argTypeStr, typeStr, argStringsStr;
267         std::tie(argStr, argTypeStr, typeStr, argStringsStr) = argsToNamesAndTypes(args);
268 
269         signalRegistrationSS << tab << tab
270                 << "object_.registerSignal(\"" << name << "\")"
271                         ".onInterface(INTERFACE_NAME)";
272 
273         if (args.size() > 0)
274         {
275             signalRegistrationSS << ".withParameters<" << typeStr << ">(" << argStringsStr << ")";
276         }
277 
278         signalRegistrationSS << annotationRegistration;
279         signalRegistrationSS << ";" << endl;
280 
281         auto nameWithCapFirstLetter = name;
282         nameWithCapFirstLetter[0] = std::toupper(nameWithCapFirstLetter[0]);
283         nameWithCapFirstLetter = mangle_name(nameWithCapFirstLetter);
284 
285         signalMethodSS << tab << "void emit" << nameWithCapFirstLetter << "(" << argTypeStr << ")" << endl
286                 << tab << "{" << endl
287                 << tab << tab << "object_.emitSignal(\"" << name << "\")"
288                         ".onInterface(INTERFACE_NAME)";
289 
290         if (!argStr.empty())
291         {
292             signalMethodSS << ".withArguments(" << argStr << ")";
293         }
294 
295         signalMethodSS << ";" << endl
296                 << tab << "}" << endl << endl;
297     }
298 
299     return std::make_tuple(signalRegistrationSS.str(), signalMethodSS.str());
300 }
301 
302 
processProperties(const Nodes & properties) const303 std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const Nodes& properties) const
304 {
305     std::ostringstream registrationSS, declarationSS;
306 
307     for (const auto& property : properties)
308     {
309         auto propertyName = property->get("name");
310         auto propertyNameSafe = mangle_name(propertyName);
311         auto propertyAccess = property->get("access");
312         auto propertySignature = property->get("type");
313 
314         auto propertyType = signature_to_type(propertySignature);
315         auto propertyArg = std::string("value");
316         auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
317 
318         auto annotations = getAnnotations(*property);
319         std::string annotationRegistration;
320         for (const auto& annotation : annotations)
321         {
322             const auto& annotationName = annotation.first;
323             const auto& annotationValue = annotation.second;
324 
325             if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true")
326                 annotationRegistration += ".markAsDeprecated()";
327             else if (annotationName == "org.freedesktop.DBus.Property.EmitsChangedSignal")
328                 annotationRegistration += ".withUpdateBehavior(" + propertyAnnotationToFlag(annotationValue) + ")";
329             else if (annotationName == "org.freedesktop.systemd1.Privileged" && annotationValue == "true")
330                 annotationRegistration += ".markAsPrivileged()";
331             else
332                 std::cerr << "Node: " << propertyName << ": "
333                           << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
334         }
335 
336         registrationSS << tab << tab << "object_.registerProperty(\""
337                 << propertyName << "\")"
338                 << ".onInterface(INTERFACE_NAME)";
339 
340         if (propertyAccess == "read" || propertyAccess == "readwrite")
341         {
342             registrationSS << ".withGetter([this](){ return this->" << propertyNameSafe << "(); })";
343         }
344 
345         if (propertyAccess == "readwrite" || propertyAccess == "write")
346         {
347             registrationSS
348                 << ".withSetter([this](" << propertyTypeArg << ")"
349                    "{ this->" << propertyNameSafe << "(" << propertyArg << "); })";
350         }
351 
352         registrationSS << annotationRegistration;
353         registrationSS << ";" << endl;
354 
355         if (propertyAccess == "read" || propertyAccess == "readwrite")
356             declarationSS << tab << "virtual " << propertyType << " " << propertyNameSafe << "() = 0;" << endl;
357         if (propertyAccess == "readwrite" || propertyAccess == "write")
358             declarationSS << tab << "virtual void " << propertyNameSafe << "(" << propertyTypeArg << ") = 0;" << endl;
359     }
360 
361     return std::make_tuple(registrationSS.str(), declarationSS.str());
362 }
363 
getAnnotations(sdbuscpp::xml::Node & node) const364 std::map<std::string, std::string> AdaptorGenerator::getAnnotations( sdbuscpp::xml::Node& node) const
365 {
366     std::map<std::string, std::string> result;
367 
368     Nodes annotations = (node)["annotation"];
369     for (const auto& annotation : annotations)
370     {
371         result[annotation->get("name")] = annotation->get("value");
372     }
373 
374     return result;
375 }
376 
propertyAnnotationToFlag(const std::string & annotationValue) const377 std::string AdaptorGenerator::propertyAnnotationToFlag(const std::string& annotationValue) const
378 {
379     return annotationValue == "true" ? "sdbus::Flags::EMITS_CHANGE_SIGNAL"
380          : annotationValue == "invalidates" ? "sdbus::Flags::EMITS_INVALIDATION_SIGNAL"
381          : annotationValue == "const" ? "sdbus::Flags::CONST_PROPERTY_VALUE"
382          : annotationValue == "false" ? "sdbus::Flags::EMITS_NO_SIGNAL"
383          : "EMITS_CHANGE_SIGNAL"; // Default
384 }
385