1/*
2 Copyright (c) 2007 Till Adam <adam@kde.org>
3 Copyright (c) 2007 Volker Krause <vkrause@kde.org>
4
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
9
10 This library is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13 License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19*/
20
21#include "typepluginloader_p.h"
22
23#include "item.h"
24#include "itemserializer_p.h"
25#include "itemserializerplugin.h"
26
27// KDE core
28#include <kdebug.h>
29#include <kmimetype.h>
30#include <kglobal.h>
31
32// Qt
33#include <QtCore/QHash>
34#include <QtCore/QString>
35#include <QtCore/QByteArray>
36#include <QtCore/QStringList>
37
38#include <boost/graph/adjacency_list.hpp>
39#include <boost/graph/topological_sort.hpp>
40
41// temporary
42#include "pluginloader_p.h"
43
44#include <vector>
45#include <cassert>
46
47static const char LEGACY_NAME[] = "legacy";
48static const char DEFAULT_NAME[] = "default";
49static const char _APPLICATION_OCTETSTREAM[] = "application/octet-stream";
50
51namespace Akonadi {
52
53K_GLOBAL_STATIC(DefaultItemSerializerPlugin, s_defaultItemSerializerPlugin)
54
55class PluginEntry
56{
57public:
58 PluginEntry()
59 : mPlugin(0)
60 {
61 }
62
63 explicit PluginEntry(const QString &identifier, QObject *plugin = 0)
64 : mIdentifier(identifier)
65 , mPlugin(plugin)
66 {
67 }
68
69 QObject *plugin() const
70 {
71 if (mPlugin) {
72 return mPlugin;
73 }
74
75 QObject *object = PluginLoader::self()->createForName(mIdentifier);
76 if (!object) {
77 kWarning() << "ItemSerializerPluginLoader: "
78 << "plugin" << mIdentifier << "is not valid!" << endl;
79
80 // we try to use the default in that case
81 mPlugin = s_defaultItemSerializerPlugin;
82 }
83
84 mPlugin = object;
85 if (!qobject_cast<ItemSerializerPlugin *>(mPlugin)) {
86 kWarning() << "ItemSerializerPluginLoader: "
87 << "plugin" << mIdentifier << "doesn't provide interface ItemSerializerPlugin!" << endl;
88
89 // we try to use the default in that case
90 mPlugin = s_defaultItemSerializerPlugin;
91 }
92
93 Q_ASSERT(mPlugin);
94
95 return mPlugin;
96 }
97
98 const char *pluginClassName() const
99 {
100 return plugin()->metaObject()->className();
101 }
102
103 QString identifier() const
104 {
105 return mIdentifier;
106 }
107
108 bool operator<(const PluginEntry &other) const
109 {
110 return mIdentifier < other.mIdentifier;
111 }
112
113 bool operator<(const QString &identifier) const
114 {
115 return mIdentifier < identifier;
116 }
117
118private:
119 QString mIdentifier;
120 mutable QObject *mPlugin;
121};
122
123static bool operator<(const QString &identifier, const PluginEntry &entry)
124{
125 return identifier < entry.identifier();
126}
127
128class MimeTypeEntry
129{
130public:
131 explicit MimeTypeEntry(const QString &mimeType)
132 : m_mimeType(mimeType)
133 , m_plugins()
134 , m_pluginsByMetaTypeId()
135 {
136 }
137
138 QString type() const
139 {
140 return m_mimeType;
141 }
142
143 void add(const QByteArray &class_, const PluginEntry &entry)
144 {
145 m_pluginsByMetaTypeId.clear(); // iterators will be invalidated by next line
146 m_plugins.insert(class_, entry);
147 }
148
149 const PluginEntry *plugin(const QByteArray &class_) const
150 {
151 const QHash<QByteArray, PluginEntry>::const_iterator it = m_plugins.find(class_);
152 return it == m_plugins.end() ? 0 : it.operator->();
153 }
154
155 const PluginEntry *defaultPlugin() const
156 {
157 // 1. If there's an explicit default plugin, use that one:
158 if (const PluginEntry *pe = plugin(DEFAULT_NAME)) {
159 return pe;
160 }
161
162 // 2. Otherwise, look through the already instantiated plugins,
163 // and return one of them (preferably not the legacy one):
164 bool sawZero = false;
165 for (QMap<int, QHash<QByteArray, PluginEntry>::const_iterator>::const_iterator it = m_pluginsByMetaTypeId.constBegin(), end = m_pluginsByMetaTypeId.constEnd(); it != end; ++it) {
166 if (it.key() == 0) {
167 sawZero = true;
168 } else if (*it != m_plugins.end()) {
169 return it->operator->();
170 }
171 }
172
173 // 3. Otherwise, look through the whole list (again, preferably not the legacy one):
174 for (QHash<QByteArray, PluginEntry>::const_iterator it = m_plugins.constBegin(), end = m_plugins.constEnd(); it != end; ++it) {
175 if (it.key() == LEGACY_NAME) {
176 sawZero = true;
177 } else {
178 return it.operator->();
179 }
180 }
181
182 // 4. take the legacy one:
183 if (sawZero) {
184 return plugin(0);
185 }
186 return 0;
187 }
188
189 const PluginEntry *plugin(int metaTypeId) const
190 {
191 const QMap<int, QHash<QByteArray, PluginEntry>::const_iterator> &c_pluginsByMetaTypeId = m_pluginsByMetaTypeId;
192 QMap<int, QHash<QByteArray, PluginEntry>::const_iterator>::const_iterator it = c_pluginsByMetaTypeId.find(metaTypeId);
193 if (it == c_pluginsByMetaTypeId.end()) {
194 it = QMap<int, QHash<QByteArray, PluginEntry>::const_iterator>::const_iterator(m_pluginsByMetaTypeId.insert(metaTypeId, m_plugins.find(metaTypeId ? QMetaType::typeName(metaTypeId) : LEGACY_NAME)));
195 }
196 return *it == m_plugins.end() ? 0 : it->operator->();
197 }
198
199 const PluginEntry *plugin(const QVector<int> &metaTypeIds, int &chosen) const
200 {
201 bool sawZero = false;
202 for (QVector<int>::const_iterator it = metaTypeIds.begin(), end = metaTypeIds.end(); it != end; ++it) {
203 if (*it == 0) {
204 sawZero = true; // skip the legacy type and see if we can find something else first
205 } else if (const PluginEntry *const entry = plugin(*it)) {
206 chosen = *it;
207 return entry;
208 }
209 }
210 if (sawZero) {
211 chosen = 0;
212 return plugin(0);
213 }
214 return 0;
215 }
216
217private:
218 QString m_mimeType;
219 QHash<QByteArray/* class */, PluginEntry> m_plugins;
220 mutable QMap<int, QHash<QByteArray, PluginEntry>::const_iterator> m_pluginsByMetaTypeId;
221};
222
223static bool operator<(const MimeTypeEntry &lhs, const MimeTypeEntry &rhs)
224{
225 return lhs.type() < rhs.type();
226}
227
228static bool operator<(const MimeTypeEntry &lhs, const QString &rhs)
229{
230 return lhs.type() < rhs;
231}
232
233static bool operator<(const QString &lhs, const MimeTypeEntry &rhs)
234{
235 return lhs < rhs.type();
236}
237
238static QString format(const QString &mimeType, const QVector<int> &metaTypeIds) {
239 if (metaTypeIds.empty()) {
240 return QLatin1String("default for ") + mimeType;
241 }
242 QStringList classTypes;
243 Q_FOREACH (int metaTypeId, metaTypeIds) {
244 classTypes.push_back(QString::fromLatin1(metaTypeId ? QMetaType::typeName(metaTypeId) : LEGACY_NAME));
245 }
246 return mimeType + QLatin1String("@{") + classTypes.join(QLatin1String(",")) + QLatin1Char('}');
247}
248
249class PluginRegistry
250{
251public:
252 PluginRegistry()
253 : mDefaultPlugin(PluginEntry(QLatin1String("application/octet-stream@QByteArray"), s_defaultItemSerializerPlugin))
254 , mOverridePlugin(0)
255 {
256 const PluginLoader *pl = PluginLoader::self();
257 if (!pl) {
258 kWarning() << "Cannot instantiate plugin loader!" << endl;
259 return;
260 }
261 const QStringList names = pl->names();
262 kDebug() << "ItemSerializerPluginLoader: "
263 << "found" << names.size() << "plugins." << endl;
264 QMap<QString, MimeTypeEntry> map;
265 QRegExp rx(QLatin1String("(.+)@(.+)"));
266 Q_FOREACH (const QString &name, names) {
267 if (rx.exactMatch(name)) {
268 KMimeType::Ptr mime = KMimeType::mimeType(rx.cap(1), KMimeType::ResolveAliases);
269 if (mime) {
270 const QString mimeType = mime->name();
271 const QByteArray classType = rx.cap(2).toLatin1();
272 QMap<QString, MimeTypeEntry>::iterator it = map.find(mimeType);
273 if (it == map.end()) {
274 it = map.insert(mimeType, MimeTypeEntry(mimeType));
275 }
276 it->add(classType, PluginEntry(name));
277 }
278 } else {
279 kDebug() << "ItemSerializerPluginLoader: "
280 << "name" << name << "doesn't look like mimetype@classtype" << endl;
281 }
282 }
283 const QString APPLICATION_OCTETSTREAM = QLatin1String(_APPLICATION_OCTETSTREAM);
284 QMap<QString, MimeTypeEntry>::iterator it = map.find(APPLICATION_OCTETSTREAM);
285 if (it == map.end()) {
286 it = map.insert(APPLICATION_OCTETSTREAM, MimeTypeEntry(APPLICATION_OCTETSTREAM));
287 }
288 it->add("QByteArray", mDefaultPlugin);
289 it->add(LEGACY_NAME, mDefaultPlugin);
290 const int size = map.size();
291 allMimeTypes.reserve(size);
292 std::copy(map.begin(), map.end(), std::back_inserter(allMimeTypes));
293 }
294
295 QObject *findBestMatch(const QString &type, const QVector<int> &metaTypeId, TypePluginLoader::Options opt)
296 {
297 if (QObject *const plugin = findBestMatch(type, metaTypeId)) { {
298 if ((opt &TypePluginLoader::NoDefault) && plugin == mDefaultPlugin.plugin()) {
299 return 0;
300 }
301 return plugin;
302 }
303 }
304 return 0;
305 }
306
307 QObject *findBestMatch(const QString &type, const QVector<int> &metaTypeIds)
308 {
309 if (mOverridePlugin) {
310 return mOverridePlugin;
311 }
312 if (QObject *const plugin = cacheLookup(type, metaTypeIds)) {
313 // plugin cached, so let's take that one
314 return plugin;
315 }
316 int chosen = -1;
317 QObject *const plugin = findBestMatchImpl(type, metaTypeIds, chosen);
318 if (metaTypeIds.empty()) {
319 if (plugin) {
320 cachedDefaultPlugins[type] = plugin;
321 }
322 }
323 if (chosen >= 0) {
324 cachedPlugins[type][chosen] = plugin;
325 }
326 return plugin;
327 }
328
329 void overrideDefaultPlugin(QObject *p)
330 {
331 mOverridePlugin = p;
332 }
333
334private:
335 QObject *findBestMatchImpl(const QString &type, const QVector<int> &metaTypeIds, int &chosen) const
336 {
337 KMimeType::Ptr mimeType = KMimeType::mimeType(type, KMimeType::ResolveAliases);
338 if (mimeType.isNull()) {
339 return mDefaultPlugin.plugin();
340 }
341
342 // step 1: find all plugins that match at all
343 QVector<int> matchingIndexes;
344 for (int i = 0, end = allMimeTypes.size(); i < end; ++i) {
345 if (mimeType->is(allMimeTypes[i].type())) {
346 matchingIndexes.append(i);
347 }
348 }
349
350 // step 2: if we have more than one match, find the most specific one using topological sort
351 QVector<int> order;
352 if (matchingIndexes.size() <= 1) {
353 order.push_back(0);
354 } else {
355 boost::adjacency_list<> graph(matchingIndexes.size());
356 for (int i = 0, end = matchingIndexes.size(); i != end; ++i) {
357 KMimeType::Ptr mimeType = KMimeType::mimeType(allMimeTypes[matchingIndexes[i]].type(), KMimeType::ResolveAliases);
358 if (mimeType.isNull()) {
359 continue;
360 }
361 for (int j = 0; j != end; ++j) {
362 if (i != j && mimeType->is(allMimeTypes[matchingIndexes[j]].type())) {
363 boost::add_edge(j, i, graph);
364 }
365 }
366 }
367
368 order.reserve(matchingIndexes.size());
369 try {
370 boost::topological_sort(graph, std::back_inserter(order));
371 } catch (boost::not_a_dag &e) {
372 kWarning() << "Mimetype tree is not a DAG!";
373 return mDefaultPlugin.plugin();
374 }
375 }
376
377 // step 3: ask each one in turn if it can handle any of the metaTypeIds:
378// kDebug() << "Looking for " << format( type, metaTypeIds );
379 for (QVector<int>::const_iterator it = order.constBegin(), end = order.constEnd(); it != end; ++it) {
380// kDebug() << " Considering serializer plugin for type" << allMimeTypes[matchingIndexes[*it]].type()
381// // << "as the closest match";
382 const MimeTypeEntry &mt = allMimeTypes[matchingIndexes[*it]];
383 if (metaTypeIds.empty()) {
384 if (const PluginEntry *const entry = mt.defaultPlugin()) {
385// kDebug() << " -> got " << entry->pluginClassName() << " and am happy with it.";
386 return entry->plugin();
387 } else {
388// kDebug() << " -> no default plugin for this mime type, trying next";
389 }
390 } else if (const PluginEntry *const entry = mt.plugin(metaTypeIds, chosen)) {
391// kDebug() << " -> got " << entry->pluginClassName() << " and am happy with it.";
392 return entry->plugin();
393 } else {
394// kDebug() << " -> can't handle any of the types, trying next";
395 }
396 }
397
398// kDebug() << " No further candidates, using default plugin";
399 // no luck? Use the default plugin
400 return mDefaultPlugin.plugin();
401 }
402
403 std::vector<MimeTypeEntry> allMimeTypes;
404 QHash<QString, QMap<int, QObject *> > cachedPlugins;
405 QHash<QString, QObject *> cachedDefaultPlugins;
406
407 // ### cache NULLs, too
408 QObject *cacheLookup(const QString &mimeType, const QVector<int> &metaTypeIds) const {
409 if (metaTypeIds.empty()) {
410 const QHash<QString, QObject *>::const_iterator hit = cachedDefaultPlugins.find(mimeType);
411 if (hit != cachedDefaultPlugins.end()) {
412 return *hit;
413 }
414 }
415
416 const QHash<QString, QMap<int, QObject *> >::const_iterator hit = cachedPlugins.find(mimeType);
417 if (hit == cachedPlugins.end()) {
418 return 0;
419 }
420 bool sawZero = false;
421 for (QVector<int>::const_iterator it = metaTypeIds.begin(), end = metaTypeIds.end(); it != end; ++it) {
422 if (*it == 0) {
423 sawZero = true; // skip the legacy type and see if we can find something else first
424 } else if (QObject *const o = hit->value(*it)) {
425 return o;
426 }
427 }
428 if (sawZero) {
429 return hit->value(0);
430 }
431 return 0;
432 }
433
434private:
435 PluginEntry mDefaultPlugin;
436 QObject *mOverridePlugin;
437};
438
439K_GLOBAL_STATIC(PluginRegistry, s_pluginRegistry)
440
441QObject *TypePluginLoader::objectForMimeTypeAndClass(const QString &mimetype, const QVector<int> &metaTypeIds, Options opt)
442{
443 return s_pluginRegistry->findBestMatch(mimetype, metaTypeIds, opt);
444}
445
446#if 0
447QObject *TypePluginLoader::legacyObjectForMimeType(const QString &mimetype) {
448 // ### impl specifically - only works b/c vector isn't used in impl ###
449 return objectForMimeTypeAndClass(mimetype, QVector<int>(1, 0));
450}
451#endif
452
453QObject *TypePluginLoader::defaultObjectForMimeType(const QString &mimetype) {
454 return objectForMimeTypeAndClass(mimetype, QVector<int>());
455}
456
457ItemSerializerPlugin *TypePluginLoader::pluginForMimeTypeAndClass(const QString &mimetype, const QVector<int> &metaTypeIds, Options opt)
458{
459 return qobject_cast<ItemSerializerPlugin *>(objectForMimeTypeAndClass(mimetype, metaTypeIds, opt));
460}
461
462#if 0
463ItemSerializerPlugin *TypePluginLoader::legacyPluginForMimeType(const QString &mimetype) {
464 ItemSerializerPlugin *plugin = qobject_cast<ItemSerializerPlugin *>(legacyObjectForMimeType(mimetype));
465 Q_ASSERT(plugin);
466 return plugin;
467}
468#endif
469
470ItemSerializerPlugin *TypePluginLoader::defaultPluginForMimeType(const QString &mimetype) {
471 ItemSerializerPlugin *plugin = qobject_cast<ItemSerializerPlugin *>(defaultObjectForMimeType(mimetype));
472 Q_ASSERT(plugin);
473 return plugin;
474}
475
476void TypePluginLoader::overridePluginLookup(QObject *p) {
477 s_pluginRegistry->overrideDefaultPlugin(p);
478}
479
480}
481