1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qmetatypesjsonprocessor_p.h"
5
6#include <QtCore/qfile.h>
7#include <QtCore/qjsonarray.h>
8#include <QtCore/qjsondocument.h>
9#include <QtCore/qqueue.h>
10
11QT_BEGIN_NAMESPACE
12
13using namespace Qt::StringLiterals;
14
15QStringList MetaTypesJsonProcessor::namespaces(const QJsonObject &classDef)
16{
17 const QString unqualified = classDef.value(key: "className"_L1).toString();
18 const QString qualified = classDef.value(key: "qualifiedClassName"_L1).toString();
19 QStringList namespaces;
20 if (qualified != unqualified) {
21 namespaces = qualified.split(sep: "::"_L1);
22 Q_ASSERT(namespaces.last() == unqualified);
23 namespaces.pop_back();
24 }
25 return namespaces;
26}
27
28bool MetaTypesJsonProcessor::processTypes(const QStringList &files)
29{
30 for (const QString &source: files) {
31 QJsonDocument metaObjects;
32 {
33 QFile f(source);
34 if (!f.open(flags: QIODevice::ReadOnly)) {
35 fprintf(stderr, format: "Error opening %s for reading\n", qPrintable(source));
36 return false;
37 }
38 QJsonParseError error = {.offset: 0, .error: QJsonParseError::NoError};
39 metaObjects = QJsonDocument::fromJson(json: f.readAll(), error: &error);
40 if (error.error != QJsonParseError::NoError) {
41 fprintf(stderr, format: "Error %d while parsing %s: %s\n", error.error, qPrintable(source),
42 qPrintable(error.errorString()));
43 return false;
44 }
45 }
46
47 if (metaObjects.isArray()) {
48 const QJsonArray metaObjectsArray = metaObjects.array();
49 for (const QJsonValue metaObject : metaObjectsArray) {
50 if (!metaObject.isObject()) {
51 fprintf(stderr, format: "Error parsing %s: JSON is not an object\n",
52 qPrintable(source));
53 return false;
54 }
55
56 processTypes(types: metaObject.toObject());
57 }
58 } else if (metaObjects.isObject()) {
59 processTypes(types: metaObjects.object());
60 } else {
61 fprintf(stderr, format: "Error parsing %s: JSON is not an object or an array\n",
62 qPrintable(source));
63 return false;
64 }
65 }
66
67 return true;
68}
69
70bool MetaTypesJsonProcessor::processForeignTypes(const QStringList &foreignTypesFiles)
71{
72 bool success = true;
73
74 for (const QString &types : foreignTypesFiles) {
75 QFile typesFile(types);
76 if (!typesFile.open(flags: QIODevice::ReadOnly)) {
77 fprintf(stderr, format: "Cannot open foreign types file %s\n", qPrintable(types));
78 success = false;
79 continue;
80 }
81
82 QJsonParseError error = {.offset: 0, .error: QJsonParseError::NoError};
83 QJsonDocument foreignMetaObjects = QJsonDocument::fromJson(json: typesFile.readAll(), error: &error);
84 if (error.error != QJsonParseError::NoError) {
85 fprintf(stderr, format: "Error %d while parsing %s: %s\n", error.error, qPrintable(types),
86 qPrintable(error.errorString()));
87 success = false;
88 continue;
89 }
90
91 const QJsonArray foreignObjectsArray = foreignMetaObjects.array();
92 for (const QJsonValue metaObject : foreignObjectsArray) {
93 if (!metaObject.isObject()) {
94 fprintf(stderr, format: "Error parsing %s: JSON is not an object\n",
95 qPrintable(types));
96 success = false;
97 continue;
98 }
99
100 processForeignTypes(types: metaObject.toObject());
101 }
102 }
103 return success;
104}
105
106static void sortStringList(QStringList *list)
107{
108 std::sort(first: list->begin(), last: list->end());
109 const auto newEnd = std::unique(first: list->begin(), last: list->end());
110 list->erase(abegin: QStringList::const_iterator(newEnd), aend: list->constEnd());
111}
112
113void MetaTypesJsonProcessor::postProcessTypes()
114{
115 sortTypes(types&: m_types);
116 sortStringList(list: &m_includes);
117}
118
119void MetaTypesJsonProcessor::postProcessForeignTypes()
120{
121 sortTypes(types&: m_foreignTypes);
122 addRelatedTypes();
123 sortStringList(list: &m_referencedTypes);
124}
125
126QString MetaTypesJsonProcessor::extractRegisteredTypes() const
127{
128 QString registrationHelper;
129 for (const auto &obj: m_types) {
130 const QString className = obj[u"className"].toString();
131 const QString foreignClassName = className+ u"Foreign";
132 const auto classInfos = obj[u"classInfos"].toArray();
133 QString qmlElement;
134 QString qmlUncreatable;
135 QString qmlAttached;
136 bool isSingleton = false;
137 bool isExplicitlyUncreatable = false;
138 for (QJsonValue entry: classInfos) {
139 const auto name = entry[u"name"].toString();
140 const auto value = entry[u"value"].toString();
141 if (name == u"QML.Element") {
142 if (value == u"auto") {
143 qmlElement = u"QML_NAMED_ELEMENT("_s + className + u")"_s;
144 } else if (value == u"anonymous") {
145 qmlElement = u"QML_ANONYMOUS"_s;
146 } else {
147 qmlElement = u"QML_NAMED_ELEMENT(" + value + u")";
148 }
149 } else if (name == u"QML.Creatable" && value == u"false") {
150 isExplicitlyUncreatable = true;
151 } else if (name == u"QML.UncreatableReason") {
152 qmlUncreatable = u"QML_UNCREATABLE(\"" + value + u"\")";
153 } else if (name == u"QML.Attached") {
154 qmlAttached = u"QML_ATTACHED("_s + value + u")";
155 } else if (name == u"QML.Singleton") {
156 isSingleton = true;
157 }
158 }
159 if (qmlElement.isEmpty())
160 continue; // no relevant entries found
161 const QString spaces = u" "_s;
162 registrationHelper += u"\nstruct "_s + foreignClassName + u"{\n Q_GADGET\n"_s;
163 registrationHelper += spaces + u"QML_FOREIGN(" + className + u")\n"_s;
164 registrationHelper += spaces + qmlElement + u"\n"_s;
165 if (isSingleton)
166 registrationHelper += spaces + u"QML_SINGLETON\n"_s;
167 if (isExplicitlyUncreatable) {
168 if (qmlUncreatable.isEmpty())
169 registrationHelper += spaces + uR"(QML_UNCREATABLE(""))" + u"n";
170 else
171 registrationHelper += spaces + qmlUncreatable + u"\n";
172 }
173 if (!qmlAttached.isEmpty())
174 registrationHelper += spaces + qmlAttached + u"\n";
175 registrationHelper += u"};\n";
176 }
177 return registrationHelper;
178}
179
180MetaTypesJsonProcessor::RegistrationMode MetaTypesJsonProcessor::qmlTypeRegistrationMode(
181 const QJsonObject &classDef)
182{
183 const QJsonArray classInfos = classDef[QLatin1String("classInfos")].toArray();
184 for (const QJsonValue info : classInfos) {
185 const QString name = info[QLatin1String("name")].toString();
186 if (name == QLatin1String("QML.Element")) {
187 if (classDef[QLatin1String("object")].toBool())
188 return ObjectRegistration;
189 if (classDef[QLatin1String("gadget")].toBool())
190 return GadgetRegistration;
191 if (classDef[QLatin1String("namespace")].toBool())
192 return NamespaceRegistration;
193 qWarning() << "Not registering classInfo which is neither an object, "
194 "nor a gadget, nor a namespace:"
195 << name;
196 break;
197 }
198 }
199 return NoRegistration;
200}
201
202static bool qualifiedClassNameLessThan(const QJsonObject &a, const QJsonObject &b)
203{
204 const QLatin1String qualifiedClassNameKey("qualifiedClassName");
205 return a.value(key: qualifiedClassNameKey).toString() <
206 b.value(key: qualifiedClassNameKey).toString();
207}
208
209void MetaTypesJsonProcessor::addRelatedTypes()
210{
211 const QLatin1String classInfosKey("classInfos");
212 const QLatin1String nameKey("name");
213 const QLatin1String qualifiedClassNameKey("qualifiedClassName");
214 const QLatin1String qmlNamePrefix("QML.");
215 const QLatin1String qmlForeignName("QML.Foreign");
216 const QLatin1String qmlExtendedName("QML.Extended");
217 const QLatin1String qmlAttachedName("QML.Attached");
218 const QLatin1String qmlSequenceName("QML.Sequence");
219 const QLatin1String valueKey("value");
220 const QLatin1String superClassesKey("superClasses");
221 const QLatin1String accessKey("access");
222 const QLatin1String publicAccess("public");
223
224 QSet<QString> processedRelatedNames;
225 QQueue<QJsonObject> typeQueue;
226 typeQueue.append(l: m_types);
227
228 const auto addRelatedName = [&](const QString &relatedName, const QStringList &namespaces) {
229 if (const QJsonObject *related = QmlTypesClassDescription::findType(
230 types: m_types, foreign: m_foreignTypes, name: relatedName, namespaces)) {
231 processedRelatedNames.insert(value: related->value(key: qualifiedClassNameKey).toString());
232 }
233 };
234
235 // First mark all classes registered from this module as already processed.
236 for (const QJsonObject &type : m_types) {
237 processedRelatedNames.insert(value: type.value(key: qualifiedClassNameKey).toString());
238 const auto classInfos = type.value(key: classInfosKey).toArray();
239 for (const QJsonValue classInfo : classInfos) {
240 const QJsonObject obj = classInfo.toObject();
241 if (obj.value(key: nameKey).toString() == qmlForeignName) {
242 addRelatedName(obj.value(key: valueKey).toString(), namespaces(classDef: type));
243 break;
244 }
245 }
246 }
247
248 // Then mark all classes registered from other modules as already processed.
249 // We don't want to generate them again for this module.
250 for (const QJsonObject &foreignType : m_foreignTypes) {
251 const auto classInfos = foreignType.value(key: classInfosKey).toArray();
252 bool seenQmlPrefix = false;
253 for (const QJsonValue classInfo : classInfos) {
254 const QJsonObject obj = classInfo.toObject();
255 const QString name = obj.value(key: nameKey).toString();
256 if (!seenQmlPrefix && name.startsWith(s: qmlNamePrefix)) {
257 processedRelatedNames.insert(value: foreignType.value(key: qualifiedClassNameKey).toString());
258 seenQmlPrefix = true;
259 }
260 if (name == qmlForeignName) {
261 addRelatedName(obj.value(key: valueKey).toString(), namespaces(classDef: foreignType));
262 break;
263 }
264 }
265 }
266
267 auto addType = [&](const QString &typeName, const QStringList &namespaces) {
268 if (const QJsonObject *other = QmlTypesClassDescription::findType(
269 types: m_types, foreign: m_foreignTypes, name: typeName, namespaces)) {
270 const QString qualifiedName = other->value(key: qualifiedClassNameKey).toString();
271 m_referencedTypes.append(t: qualifiedName);
272 if (!processedRelatedNames.contains(value: qualifiedName)) {
273 processedRelatedNames.insert(value: qualifiedName);
274 m_types.insert(
275 before: std::lower_bound(
276 first: m_types.begin(), last: m_types.end(), val: *other, comp: qualifiedClassNameLessThan),
277 t: *other);
278 typeQueue.enqueue(t: *other);
279 }
280 return true;
281 }
282 processedRelatedNames.insert(value: typeName);
283 return false;
284 };
285
286 // Then recursively iterate the super types and attached types, marking the
287 // ones we are interested in as related.
288 while (!typeQueue.isEmpty()) {
289 const QJsonObject classDef = typeQueue.dequeue();
290 const QStringList namespaces = MetaTypesJsonProcessor::namespaces(classDef);
291
292 const auto classInfos = classDef.value(key: classInfosKey).toArray();
293 for (const QJsonValue classInfo : classInfos) {
294 const QJsonObject obj = classInfo.toObject();
295 const QString objNameValue = obj.value(key: nameKey).toString();
296 if (objNameValue == qmlAttachedName || objNameValue == qmlSequenceName
297 || objNameValue == qmlExtendedName) {
298 addType(obj.value(key: valueKey).toString(), namespaces);
299 } else if (objNameValue == qmlForeignName) {
300 const QString foreignClassName = obj.value(key: valueKey).toString();
301 if (const QJsonObject *other = QmlTypesClassDescription::findType(
302 types: m_foreignTypes, foreign: {}, name: foreignClassName, namespaces)) {
303 const auto otherSupers = other->value(key: superClassesKey).toArray();
304 const QStringList otherNamespaces = MetaTypesJsonProcessor::namespaces(classDef: *other);
305 if (!otherSupers.isEmpty()) {
306 const QJsonObject otherSuperObject = otherSupers.first().toObject();
307 if (otherSuperObject.value(key: accessKey).toString() == publicAccess)
308 addType(otherSuperObject.value(key: nameKey).toString(), otherNamespaces);
309 }
310
311 const auto otherClassInfos = other->value(key: classInfosKey).toArray();
312 for (const QJsonValue otherClassInfo : otherClassInfos) {
313 const QJsonObject obj = otherClassInfo.toObject();
314 const QString objNameValue = obj.value(key: nameKey).toString();
315 if (objNameValue == qmlAttachedName || objNameValue == qmlSequenceName
316 || objNameValue == qmlExtendedName) {
317 addType(obj.value(key: valueKey).toString(), otherNamespaces);
318 break;
319 }
320 // No, you cannot chain QML_FOREIGN declarations. Sorry.
321 }
322 }
323 }
324 }
325
326 const auto supers = classDef.value(key: superClassesKey).toArray();
327 for (const QJsonValue super : supers) {
328 const QJsonObject superObject = super.toObject();
329 if (superObject.value(key: accessKey).toString() == publicAccess)
330 addType(superObject.value(key: nameKey).toString(), namespaces);
331 }
332 }
333}
334
335void MetaTypesJsonProcessor::sortTypes(QVector<QJsonObject> &types)
336{
337 std::sort(first: types.begin(), last: types.end(), comp: qualifiedClassNameLessThan);
338}
339
340QString MetaTypesJsonProcessor::resolvedInclude(const QString &include)
341{
342 return (m_privateIncludes && include.endsWith(s: QLatin1String("_p.h")))
343 ? QLatin1String("private/") + include
344 : include;
345}
346
347void MetaTypesJsonProcessor::processTypes(const QJsonObject &types)
348{
349 const QString include = resolvedInclude(include: types[QLatin1String("inputFile")].toString());
350 const QJsonArray classes = types[QLatin1String("classes")].toArray();
351 for (const QJsonValue cls : classes) {
352 QJsonObject classDef = cls.toObject();
353 classDef.insert(key: QLatin1String("inputFile"), value: include);
354
355 switch (qmlTypeRegistrationMode(classDef)) {
356 case NamespaceRegistration:
357 case GadgetRegistration:
358 case ObjectRegistration: {
359 if (!include.endsWith(s: QLatin1String(".h"))
360 && !include.endsWith(s: QLatin1String(".hpp"))
361 && !include.endsWith(s: QLatin1String(".hxx"))
362 && !include.endsWith(s: QLatin1String(".hh"))
363 && !include.endsWith(s: u".py")
364 && include.contains(c: QLatin1Char('.'))) {
365 fprintf(stderr,
366 format: "Class %s is declared in %s, which appears not to be a header.\n"
367 "The compilation of its registration to QML may fail.\n",
368 qPrintable(classDef.value(QLatin1String("qualifiedClassName"))
369 .toString()),
370 qPrintable(include));
371 }
372 m_includes.append(t: include);
373 m_types.append(t: classDef);
374 break;
375 }
376 case NoRegistration:
377 m_foreignTypes.append(t: classDef);
378 break;
379 }
380 }
381}
382
383void MetaTypesJsonProcessor::processForeignTypes(const QJsonObject &types)
384{
385 const QString include = resolvedInclude(include: types[QLatin1String("inputFile")].toString());
386 const QJsonArray classes = types[QLatin1String("classes")].toArray();
387 for (const QJsonValue cls : classes) {
388 QJsonObject classDef = cls.toObject();
389 classDef.insert(key: QLatin1String("inputFile"), value: include);
390 m_foreignTypes.append(t: classDef);
391 }
392}
393
394QT_END_NAMESPACE
395

source code of qtdeclarative/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp