Revert "Allow moc to handle symbols that have been redefined."
[profile/ivi/qtbase.git] / src / tools / qdbuscpp2xml / qdbuscpp2xml.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <QByteArray>
43 #include <QString>
44 #include <QVarLengthArray>
45 #include <QFile>
46 #include <QList>
47 #include <QBuffer>
48 #include <QRegExp>
49 #include <QVector>
50
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <stdlib.h>
55
56 #include "qdbusconnection.h"    // for the Export* flags
57 #include "qdbusconnection_p.h"    // for the qDBusCheckAsyncTag
58
59 // copied from dbus-protocol.h:
60 static const char docTypeHeader[] =
61     "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
62     "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n";
63
64 #define ANNOTATION_NO_WAIT      "org.freedesktop.DBus.Method.NoReply"
65 #define QCLASSINFO_DBUS_INTERFACE       "D-Bus Interface"
66 #define QCLASSINFO_DBUS_INTROSPECTION   "D-Bus Introspection"
67
68 #include "qdbusmetatype_p.h"
69 #include "qdbusmetatype.h"
70 #include "qdbusutil_p.h"
71
72 #include "moc.h"
73 #include "generator.h"
74 #include "preprocessor.h"
75
76 #define PROGRAMNAME     "qdbuscpp2xml"
77 #define PROGRAMVERSION  "0.2"
78 #define PROGRAMCOPYRIGHT "Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)."
79
80 static QString outputFile;
81 static int flags;
82
83 static const char help[] =
84     "Usage: " PROGRAMNAME " [options...] [files...]\n"
85     "Parses the C++ source or header file containing a QObject-derived class and\n"
86     "produces the D-Bus Introspection XML."
87     "\n"
88     "Options:\n"
89     "  -p|-s|-m       Only parse scriptable Properties, Signals and Methods (slots)\n"
90     "  -P|-S|-M       Parse all Properties, Signals and Methods (slots)\n"
91     "  -a             Output all scriptable contents (equivalent to -psm)\n"
92     "  -A             Output all contents (equivalent to -PSM)\n"
93     "  -o <filename>  Write the output to file <filename>\n"
94     "  -h             Show this information\n"
95     "  -V             Show the program version and quit.\n"
96     "\n";
97
98
99 int qDBusParametersForMethod(const FunctionDef &mm, QVector<int>& metaTypes)
100 {
101     QList<QByteArray> parameterTypes;
102
103     foreach (const ArgumentDef &arg, mm.arguments)
104         parameterTypes.append(arg.normalizedType);
105
106     return qDBusParametersForMethod(parameterTypes, metaTypes);
107 }
108
109
110 static inline QString typeNameToXml(const char *typeName)
111 {
112     QString plain = QLatin1String(typeName);
113     return plain.toHtmlEscaped();
114 }
115
116 static QString addFunction(const FunctionDef &mm, bool isSignal = false) {
117
118     QString xml = QString::fromLatin1("    <%1 name=\"%2\">\n")
119                   .arg(isSignal ? QLatin1String("signal") : QLatin1String("method"))
120                   .arg(QLatin1String(mm.name));
121
122     // check the return type first
123     int typeId = QMetaType::type(mm.normalizedType.constData());
124     if (typeId != QMetaType::Void) {
125         if (typeId) {
126             const char *typeName = QDBusMetaType::typeToSignature(typeId);
127             if (typeName) {
128                 xml += QString::fromLatin1("      <arg type=\"%1\" direction=\"out\"/>\n")
129                         .arg(typeNameToXml(typeName));
130
131                     // do we need to describe this argument?
132                     if (QDBusMetaType::signatureToType(typeName) == QVariant::Invalid)
133                         xml += QString::fromLatin1("      <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"%1\"/>\n")
134                             .arg(typeNameToXml(mm.normalizedType.constData()));
135             } else {
136                 return QString();
137             }
138         } else if (!mm.normalizedType.isEmpty()) {
139             return QString();           // wasn't a valid type
140         }
141     }
142     QList<ArgumentDef> names = mm.arguments;
143     QVector<int> types;
144     int inputCount = qDBusParametersForMethod(mm, types);
145     if (inputCount == -1)
146         return QString();           // invalid form
147     if (isSignal && inputCount + 1 != types.count())
148         return QString();           // signal with output arguments?
149     if (isSignal && types.at(inputCount) == QDBusMetaTypeId::message)
150         return QString();           // signal with QDBusMessage argument?
151
152     bool isScriptable = mm.isScriptable;
153     for (int j = 1; j < types.count(); ++j) {
154         // input parameter for a slot or output for a signal
155         if (types.at(j) == QDBusMetaTypeId::message) {
156             isScriptable = true;
157             continue;
158         }
159
160         QString name;
161         if (!names.at(j - 1).name.isEmpty())
162             name = QString::fromLatin1("name=\"%1\" ").arg(QString::fromLatin1(names.at(j - 1).name));
163
164         bool isOutput = isSignal || j > inputCount;
165
166         const char *signature = QDBusMetaType::typeToSignature(types.at(j));
167         xml += QString::fromLatin1("      <arg %1type=\"%2\" direction=\"%3\"/>\n")
168                 .arg(name)
169                 .arg(QLatin1String(signature))
170                 .arg(isOutput ? QLatin1String("out") : QLatin1String("in"));
171
172         // do we need to describe this argument?
173         if (QDBusMetaType::signatureToType(signature) == QVariant::Invalid) {
174             const char *typeName = QMetaType::typeName(types.at(j));
175             xml += QString::fromLatin1("      <annotation name=\"org.qtproject.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n")
176                     .arg(isOutput ? QLatin1String("Out") : QLatin1String("In"))
177                     .arg(isOutput && !isSignal ? j - inputCount : j - 1)
178                     .arg(typeNameToXml(typeName));
179         }
180     }
181
182     int wantedMask;
183     if (isScriptable)
184         wantedMask = isSignal ? QDBusConnection::ExportScriptableSignals
185                               : QDBusConnection::ExportScriptableSlots;
186     else
187         wantedMask = isSignal ? QDBusConnection::ExportNonScriptableSignals
188                               : QDBusConnection::ExportNonScriptableSlots;
189     if ((flags & wantedMask) != wantedMask)
190         return QString();
191
192     if (qDBusCheckAsyncTag(mm.tag.constData()))
193         // add the no-reply annotation
194         xml += QLatin1String("      <annotation name=\"" ANNOTATION_NO_WAIT "\""
195                               " value=\"true\"/>\n");
196
197     QString retval = xml;
198     retval += QString::fromLatin1("    </%1>\n")
199               .arg(isSignal ? QLatin1String("signal") : QLatin1String("method"));
200
201     return retval;
202 }
203
204
205 static QString generateInterfaceXml(const ClassDef *mo)
206 {
207     QString retval;
208
209     // start with properties:
210     if (flags & (QDBusConnection::ExportScriptableProperties |
211                  QDBusConnection::ExportNonScriptableProperties)) {
212         static const char *accessvalues[] = {0, "read", "write", "readwrite"};
213         foreach (const PropertyDef &mp, mo->propertyList) {
214             if (!((!mp.scriptable.isEmpty() && (flags & QDBusConnection::ExportScriptableProperties)) ||
215                   (!mp.scriptable.isEmpty() && (flags & QDBusConnection::ExportNonScriptableProperties))))
216                 continue;
217
218             int access = 0;
219             if (!mp.read.isEmpty())
220                 access |= 1;
221             if (!mp.write.isEmpty())
222                 access |= 2;
223
224             int typeId = QMetaType::type(mp.type.constData());
225             if (!typeId)
226                 continue;
227             const char *signature = QDBusMetaType::typeToSignature(typeId);
228             if (!signature)
229                 continue;
230
231             retval += QString::fromLatin1("    <property name=\"%1\" type=\"%2\" access=\"%3\"")
232                       .arg(QLatin1String(mp.name))
233                       .arg(QLatin1String(signature))
234                       .arg(QLatin1String(accessvalues[access]));
235
236             if (QDBusMetaType::signatureToType(signature) == QVariant::Invalid) {
237                 retval += QString::fromLatin1(">\n      <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"%3\"/>\n    </property>\n")
238                           .arg(typeNameToXml(mp.type.constData()));
239             } else {
240                 retval += QLatin1String("/>\n");
241             }
242         }
243     }
244
245     // now add methods:
246
247     if (flags & (QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportNonScriptableSignals)) {
248         foreach (const FunctionDef &mm, mo->signalList) {
249             if (mm.wasCloned)
250                 continue;
251
252             retval += addFunction(mm, true);
253         }
254     }
255
256     if (flags & (QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportNonScriptableSlots)) {
257         foreach (const FunctionDef &slot, mo->slotList) {
258             if (slot.access == FunctionDef::Public)
259               retval += addFunction(slot);
260         }
261         foreach (const FunctionDef &method, mo->methodList) {
262             if (method.access == FunctionDef::Public)
263               retval += addFunction(method);
264         }
265     }
266     return retval;
267 }
268
269 QString qDBusInterfaceFromClassDef(const ClassDef *mo)
270 {
271     QString interface;
272
273     foreach (const ClassInfoDef &cid, mo->classInfoList) {
274         if (cid.name == QCLASSINFO_DBUS_INTERFACE)
275             return QString::fromUtf8(cid.value);
276     }
277     interface = QLatin1String(mo->classname);
278     interface.replace(QLatin1String("::"), QLatin1String("."));
279
280     if (interface.startsWith(QLatin1String("QDBus"))) {
281         interface.prepend(QLatin1String("org.qtproject.QtDBus."));
282     } else if (interface.startsWith(QLatin1Char('Q')) &&
283                 interface.length() >= 2 && interface.at(1).isUpper()) {
284         // assume it's Qt
285         interface.prepend(QLatin1String("local.org.qtproject.Qt."));
286     } else {
287         interface.prepend(QLatin1String("local."));
288     }
289
290     return interface;
291 }
292
293
294 QString qDBusGenerateClassDefXml(const ClassDef *cdef)
295 {
296     foreach (const ClassInfoDef &cid, cdef->classInfoList) {
297         if (cid.name == QCLASSINFO_DBUS_INTROSPECTION)
298             return QString::fromUtf8(cid.value);
299     }
300
301     // generate the interface name from the meta object
302     QString interface = qDBusInterfaceFromClassDef(cdef);
303
304     QString xml = generateInterfaceXml(cdef);
305
306     if (xml.isEmpty())
307         return QString();       // don't add an empty interface
308     return QString::fromLatin1("  <interface name=\"%1\">\n%2  </interface>\n")
309         .arg(interface, xml);
310 }
311
312 static void showHelp()
313 {
314     printf("%s", help);
315     exit(0);
316 }
317
318 static void showVersion()
319 {
320     printf("%s version %s\n", PROGRAMNAME, PROGRAMVERSION);
321     printf("D-Bus QObject-to-XML converter\n");
322     exit(0);
323 }
324
325 static void parseCmdLine(QStringList &arguments)
326 {
327     flags = 0;
328     for (int i = 0; i < arguments.count(); ++i) {
329         const QString arg = arguments.at(i);
330
331         if (arg == QLatin1String("--help"))
332             showHelp();
333
334         if (!arg.startsWith(QLatin1Char('-')))
335             continue;
336
337         char c = arg.count() == 2 ? arg.at(1).toLatin1() : char(0);
338         switch (c) {
339         case 'P':
340             flags |= QDBusConnection::ExportNonScriptableProperties;
341             // fall through
342         case 'p':
343             flags |= QDBusConnection::ExportScriptableProperties;
344             break;
345
346         case 'S':
347             flags |= QDBusConnection::ExportNonScriptableSignals;
348             // fall through
349         case 's':
350             flags |= QDBusConnection::ExportScriptableSignals;
351             break;
352
353         case 'M':
354             flags |= QDBusConnection::ExportNonScriptableSlots;
355             // fall through
356         case 'm':
357             flags |= QDBusConnection::ExportScriptableSlots;
358             break;
359
360         case 'A':
361             flags |= QDBusConnection::ExportNonScriptableContents;
362             // fall through
363         case 'a':
364             flags |= QDBusConnection::ExportScriptableContents;
365             break;
366
367         case 'o':
368             if (arguments.count() < i + 2 || arguments.at(i + 1).startsWith(QLatin1Char('-'))) {
369                 printf("-o expects a filename\n");
370                 exit(1);
371             }
372             outputFile = arguments.takeAt(i + 1);
373             break;
374
375         case 'h':
376         case '?':
377             showHelp();
378             break;
379
380         case 'V':
381             showVersion();
382             break;
383
384         default:
385             printf("unknown option: \"%s\"\n", qPrintable(arg));
386             exit(1);
387         }
388     }
389
390     if (flags == 0)
391         flags = QDBusConnection::ExportScriptableContents
392                 | QDBusConnection::ExportNonScriptableContents;
393 }
394
395 int main(int argc, char **argv)
396 {
397     QStringList args;
398     for (int n = 1; n < argc; ++n)
399         args.append(QString::fromLocal8Bit(argv[n]));
400     parseCmdLine(args);
401
402     QList<ClassDef> classes;
403
404     for (int i = 0; i < args.count(); ++i) {
405         const QString arg = args.at(i);
406
407         if (arg.startsWith(QLatin1Char('-')))
408             continue;
409
410         QFile f(arg);
411         if (!f.open(QIODevice::ReadOnly|QIODevice::Text)) {
412             fprintf(stderr, PROGRAMNAME ": could not open '%s': %s\n",
413                     qPrintable(arg), qPrintable(f.errorString()));
414             return 1;
415         }
416
417         Preprocessor pp;
418         Moc moc;
419         pp.macros["Q_MOC_RUN"];
420         pp.macros["__cplusplus"];
421
422         const QByteArray filename = QFile::decodeName(argv[i]).toLatin1();
423
424         moc.filename = filename;
425         moc.currentFilenames.push(filename);
426
427         moc.symbols = pp.preprocessed(moc.filename, &f);
428         moc.parse();
429
430         if (moc.classList.isEmpty())
431             return 0;
432         classes = moc.classList;
433
434         f.close();
435     }
436
437     QFile output;
438     if (outputFile.isEmpty()) {
439         output.open(stdout, QIODevice::WriteOnly);
440     } else {
441         output.setFileName(outputFile);
442         if (!output.open(QIODevice::WriteOnly)) {
443             fprintf(stderr, PROGRAMNAME ": could not open output file '%s': %s",
444                     qPrintable(outputFile), qPrintable(output.errorString()));
445             return 1;
446         }
447     }
448
449     output.write(docTypeHeader);
450     output.write("<node>\n");
451     foreach (const ClassDef &cdef, classes) {
452         QString xml = qDBusGenerateClassDefXml(&cdef);
453         output.write(xml.toLocal8Bit());
454     }
455     output.write("</node>\n");
456
457     return 0;
458 }
459