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 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | using namespace Qt::StringLiterals; |
14 | |
15 | QStringList 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 | |
28 | bool 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 | |
70 | bool 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 | |
106 | static 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 | |
113 | void MetaTypesJsonProcessor::postProcessTypes() |
114 | { |
115 | sortTypes(types&: m_types); |
116 | sortStringList(list: &m_includes); |
117 | } |
118 | |
119 | void MetaTypesJsonProcessor::postProcessForeignTypes() |
120 | { |
121 | sortTypes(types&: m_foreignTypes); |
122 | addRelatedTypes(); |
123 | sortStringList(list: &m_referencedTypes); |
124 | } |
125 | |
126 | QString MetaTypesJsonProcessor::() 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 | |
180 | MetaTypesJsonProcessor::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 | |
202 | static 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 | |
209 | void 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 | |
335 | void MetaTypesJsonProcessor::sortTypes(QVector<QJsonObject> &types) |
336 | { |
337 | std::sort(first: types.begin(), last: types.end(), comp: qualifiedClassNameLessThan); |
338 | } |
339 | |
340 | QString MetaTypesJsonProcessor::resolvedInclude(const QString &include) |
341 | { |
342 | return (m_privateIncludes && include.endsWith(s: QLatin1String("_p.h" ))) |
343 | ? QLatin1String("private/" ) + include |
344 | : include; |
345 | } |
346 | |
347 | void 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 | |
383 | void 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 | |
394 | QT_END_NAMESPACE |
395 | |