Remove the usage of deprecated qdoc macros.
[profile/ivi/qtdeclarative.git] / src / qml / qml / qqmlimport.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 QtQml module 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 "qqmlimport_p.h"
43
44 #include <QtCore/qdebug.h>
45 #include <QtCore/qdir.h>
46 #include <QtCore/qfileinfo.h>
47 #include <QtCore/qpluginloader.h>
48 #include <QtCore/qlibraryinfo.h>
49 #include <QtQml/qqmlextensioninterface.h>
50 #include <private/qqmlglobal_p.h>
51 #include <private/qqmltypenamecache_p.h>
52 #include <private/qqmlengine_p.h>
53
54 QT_BEGIN_NAMESPACE
55
56 DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
57 DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
58
59 static bool greaterThan(const QString &s1, const QString &s2)
60 {
61     return s1 > s2;
62 }
63
64 QString resolveLocalUrl(const QString &url, const QString &relative)
65 {
66     if (relative.contains(QLatin1Char(':'))) {
67         // contains a host name
68         return QUrl(url).resolved(QUrl(relative)).toString();
69     } else if (relative.isEmpty()) {
70         return url;
71     } else if (relative.at(0) == QLatin1Char('/') || !url.contains(QLatin1Char('/'))) {
72         return relative;
73     } else {
74         if (relative == QLatin1String("."))
75             return url.left(url.lastIndexOf(QLatin1Char('/')) + 1);
76         else if (relative.startsWith(QLatin1String("./")))
77             return url.left(url.lastIndexOf(QLatin1Char('/')) + 1) + relative.mid(2);
78         return url.left(url.lastIndexOf(QLatin1Char('/')) + 1) + relative;
79     }
80 }
81
82
83
84 typedef QMap<QString, QString> StringStringMap;
85 Q_GLOBAL_STATIC(StringStringMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri
86
87 class QQmlImportedNamespace 
88 {
89 public:
90     struct Data {
91         QString uri;
92         QString url;
93         int majversion;
94         int minversion;
95         bool isLibrary;
96         QQmlDirComponents qmlDirComponents;
97         QQmlDirScripts qmlDirScripts;
98     };
99     QList<Data> imports;
100
101
102     bool find_helper(QQmlTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor,
103                                  QQmlType** type_return, QString* url_return,
104                                  QString *base = 0, bool *typeRecursionDetected = 0);
105     bool find(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
106               QString* url_return, QString *base = 0, QList<QQmlError> *errors = 0);
107 };
108
109 class QQmlImportsPrivate {
110 public:
111     QQmlImportsPrivate(QQmlTypeLoader *loader);
112     ~QQmlImportsPrivate();
113
114     bool importExtension(const QString &absoluteFilePath, const QString &uri, 
115                          QQmlImportDatabase *database, QQmlDirComponents* components, 
116                          QQmlDirScripts *scripts,
117                          QList<QQmlError> *errors);
118
119     QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database);
120     bool add(const QQmlDirComponents &qmldircomponentsnetwork, 
121              const QString& uri_arg, const QString& prefix, 
122              int vmaj, int vmin, QQmlScript::Import::Type importType, 
123              QQmlImportDatabase *database, QList<QQmlError> *errors);
124     bool find(const QString& type, int *vmajor, int *vminor,
125               QQmlType** type_return, QString* url_return, QList<QQmlError> *errors);
126
127     QQmlImportedNamespace *findNamespace(const QString& type);
128
129     QUrl baseUrl;
130     QString base;
131     int ref;
132
133     QSet<QString> qmlDirFilesForWhichPluginsHaveBeenLoaded;
134     QQmlImportedNamespace unqualifiedset;
135     QHash<QString,QQmlImportedNamespace* > set;
136     QQmlTypeLoader *typeLoader;
137 };
138
139 /*!
140 \class QQmlImports
141 \brief The QQmlImports class encapsulates one QML document's import statements.
142 \internal
143 */
144 QQmlImports::QQmlImports(const QQmlImports &copy) 
145 : d(copy.d)
146 {
147     ++d->ref;
148 }
149
150 QQmlImports &
151 QQmlImports::operator =(const QQmlImports &copy)
152 {
153     ++copy.d->ref;
154     if (--d->ref == 0)
155         delete d;
156     d = copy.d;
157     return *this;
158 }
159
160 QQmlImports::QQmlImports(QQmlTypeLoader *typeLoader)
161     : d(new QQmlImportsPrivate(typeLoader))
162 {
163 }
164
165 QQmlImports::~QQmlImports()
166 {
167     if (--d->ref == 0)
168         delete d;
169 }
170
171 /*!
172   Sets the base URL to be used for all relative file imports added.
173 */
174 void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString)
175 {
176     d->baseUrl = url;
177
178     if (urlString.isEmpty()) {
179         d->base = url.toString();
180     } else {
181         //Q_ASSERT(url.toString() == urlString);
182         d->base = urlString;
183     }
184 }
185
186 /*!
187   Returns the base URL to be used for all relative file imports added.
188 */
189 QUrl QQmlImports::baseUrl() const
190 {
191     return d->baseUrl;
192 }
193
194 void QQmlImports::populateCache(QQmlTypeNameCache *cache, QQmlEngine *engine) const
195 {
196     const QQmlImportedNamespace &set = d->unqualifiedset;
197
198     for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
199         const QQmlImportedNamespace::Data &data = set.imports.at(ii);
200         QQmlTypeModule *module = QQmlMetaType::typeModule(data.uri, data.majversion);
201         if (module)
202             cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, data.minversion));
203     }
204
205     for (QHash<QString,QQmlImportedNamespace* >::ConstIterator iter = d->set.begin();
206          iter != d->set.end(); 
207          ++iter) {
208
209         const QQmlImportedNamespace &set = *iter.value();
210         for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
211             const QQmlImportedNamespace::Data &data = set.imports.at(ii);
212             QQmlTypeModule *module = QQmlMetaType::typeModule(data.uri, data.majversion);
213             if (module) {
214                 QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()];
215                 import.modules.append(QQmlTypeModuleVersion(module, data.minversion));
216             }
217
218             QQmlMetaType::ModuleApi moduleApi = QQmlMetaType::moduleApi(data.uri, data.majversion, data.minversion);
219             if (moduleApi.script || moduleApi.qobject) {
220                 QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()];
221                 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
222                 import.moduleApi = ep->moduleApiInstance(moduleApi);
223             }
224         }
225     }
226 }
227
228 QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
229 {
230     QList<QQmlImports::ScriptReference> scripts;
231
232     const QQmlImportedNamespace &set = d->unqualifiedset;
233
234     for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
235         const QQmlImportedNamespace::Data &data = set.imports.at(ii);
236
237         foreach (const QQmlDirParser::Script &script, data.qmlDirScripts) {
238             ScriptReference ref;
239             ref.nameSpace = script.nameSpace;
240             ref.location = QUrl(data.url).resolved(QUrl(script.fileName));
241             scripts.append(ref);
242         }
243     }
244
245     for (QHash<QString,QQmlImportedNamespace* >::ConstIterator iter = d->set.constBegin();
246          iter != d->set.constEnd();
247          ++iter) {
248         const QQmlImportedNamespace &set = *iter.value();
249
250         for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
251             const QQmlImportedNamespace::Data &data = set.imports.at(ii);
252
253             foreach (const QQmlDirParser::Script &script, data.qmlDirScripts) {
254                 ScriptReference ref;
255                 ref.nameSpace = script.nameSpace;
256                 ref.qualifier = iter.key();
257                 ref.location = QUrl(data.url).resolved(QUrl(script.fileName));
258                 scripts.append(ref);
259             }
260         }
261     }
262
263     return scripts;
264 }
265
266 /*!
267   \internal
268
269   The given (namespace qualified) \a type is resolved to either
270   \list
271   \li a QQmlImportedNamespace stored at \a ns_return,
272   \li a QQmlType stored at \a type_return, or
273   \li a component located at \a url_return.
274   \endlist
275
276   If any return pointer is 0, the corresponding search is not done.
277
278   \sa addImport()
279 */
280 bool QQmlImports::resolveType(const QString& type,
281                                       QQmlType** type_return, QString* url_return, int *vmaj, int *vmin,
282                                       QQmlImportedNamespace** ns_return, QList<QQmlError> *errors) const
283 {
284     QQmlImportedNamespace* ns = d->findNamespace(type);
285     if (ns) {
286         if (ns_return)
287             *ns_return = ns;
288         return true;
289     }
290     if (type_return || url_return) {
291         if (d->find(type,vmaj,vmin,type_return,url_return, errors)) {
292             if (qmlImportTrace()) {
293                 if (type_return && *type_return && url_return && !url_return->isEmpty())
294                     qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " 
295                                        << type << " => " << (*type_return)->typeName() << " " << *url_return;
296                 if (type_return && *type_return)
297                     qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " 
298                                        << type << " => " << (*type_return)->typeName();
299                 if (url_return && !url_return->isEmpty())
300                     qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " 
301                                        << type << " => " << *url_return;
302             }
303             return true;
304         }
305     }
306     return false;
307 }
308
309 /*!
310   \internal
311
312   Searching \e only in the namespace \a ns (previously returned in a call to
313   resolveType(), \a type is found and returned to either
314   a QQmlType stored at \a type_return, or
315   a component located at \a url_return.
316
317   If either return pointer is 0, the corresponding search is not done.
318 */
319 bool QQmlImports::resolveType(QQmlImportedNamespace* ns, const QString& type,
320                                       QQmlType** type_return, QString* url_return,
321                                       int *vmaj, int *vmin) const
322 {
323     return ns->find(d->typeLoader,type,vmaj,vmin,type_return,url_return);
324 }
325
326 bool QQmlImportedNamespace::find_helper(QQmlTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor,
327                                  QQmlType** type_return, QString* url_return,
328                                  QString *base, bool *typeRecursionDetected)
329 {
330     int vmaj = data.majversion;
331     int vmin = data.minversion;
332
333     if (vmaj >= 0 && vmin >= 0) {
334         QString qt = data.uri + QLatin1Char('/') + type;
335         QQmlType *t = QQmlMetaType::qmlType(qt,vmaj,vmin);
336         if (t) {
337             if (vmajor) *vmajor = vmaj;
338             if (vminor) *vminor = vmin;
339             if (type_return)
340                 *type_return = t;
341             return true;
342         }
343     }
344
345     const QQmlDirComponents &qmldircomponents = data.qmlDirComponents;
346     bool typeWasDeclaredInQmldir = false;
347     if (!qmldircomponents.isEmpty()) {
348         foreach (const QQmlDirParser::Component &c, qmldircomponents) {
349             if (c.typeName == type) {
350                 typeWasDeclaredInQmldir = true;
351                 // importing version -1 means import ALL versions
352                 if ((vmaj == -1) || (c.majorVersion == vmaj && vmin >= c.minorVersion)) {
353                     QString url(data.url + type + QLatin1String(".qml"));
354                     QString candidate = resolveLocalUrl(url, c.fileName);
355                     if (c.internal && base) {
356                         if (resolveLocalUrl(*base, c.fileName) != candidate)
357                             continue; // failed attempt to access an internal type
358                     }
359                     if (base && *base == candidate) {
360                         if (typeRecursionDetected)
361                             *typeRecursionDetected = true;
362                         continue; // no recursion
363                     }
364                     if (url_return)
365                         *url_return = candidate;
366                     return true;
367                 }
368             }
369         }
370     }
371
372     if (!typeWasDeclaredInQmldir && !data.isLibrary) {
373         // XXX search non-files too! (eg. zip files, see QT-524)
374         QString url(data.url + type + QLatin1String(".qml"));
375         QString file = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
376         if (!typeLoader->absoluteFilePath(file).isEmpty()) {
377             if (base && *base == url) { // no recursion
378                 if (typeRecursionDetected)
379                     *typeRecursionDetected = true;
380             } else {
381                 if (url_return)
382                     *url_return = url;
383                 return true;
384             }
385         }
386     }
387     return false;
388 }
389
390 QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
391     : ref(1), typeLoader(loader)
392 {
393 }
394
395 QQmlImportsPrivate::~QQmlImportsPrivate()
396 {
397     foreach (QQmlImportedNamespace* s, set.values())
398         delete s;
399 }
400
401 bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const QString &uri,
402                                                  QQmlImportDatabase *database,
403                                                  QQmlDirComponents* components,
404                                                  QQmlDirScripts* scripts,
405                                                  QList<QQmlError> *errors)
406 {
407     const QQmlDirParser *qmldirParser = typeLoader->qmlDirParser(absoluteFilePath);
408     if (qmldirParser->hasError()) {
409         if (errors) {
410             const QList<QQmlError> qmldirErrors = qmldirParser->errors(uri);
411             for (int i = 0; i < qmldirErrors.size(); ++i)
412                 errors->prepend(qmldirErrors.at(i));
413         }
414         return false;
415     }
416
417     if (qmlImportTrace())
418         qDebug().nospace() << "QQmlImports(" << qPrintable(base) << "::importExtension: "
419                            << "loaded " << absoluteFilePath;
420
421     if (! qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(absoluteFilePath)) {
422         qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(absoluteFilePath);
423
424         QString qmldirPath = absoluteFilePath;
425         int slash = absoluteFilePath.lastIndexOf(QLatin1Char('/'));
426         if (slash > 0)
427             qmldirPath.truncate(slash);
428         foreach (const QQmlDirParser::Plugin &plugin, qmldirParser->plugins()) {
429
430             QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name);
431             if (!resolvedFilePath.isEmpty()) {
432                 if (!database->importPlugin(resolvedFilePath, uri, errors)) {
433                     if (errors) {
434                         // XXX TODO: should we leave the import plugin error alone?
435                         // Here, we pop it off the top and coalesce it into this error's message.
436                         // The reason is that the lower level may add url and line/column numbering information.
437                         QQmlError poppedError = errors->takeFirst();
438                         QQmlError error;
439                         error.setDescription(QQmlImportDatabase::tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description()));
440                         error.setUrl(QUrl::fromLocalFile(absoluteFilePath));
441                         errors->prepend(error);
442                     }
443                     return false;
444                 }
445             } else {
446                 if (errors) {
447                     QQmlError error;
448                     error.setDescription(QQmlImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name));
449                     error.setUrl(QUrl::fromLocalFile(absoluteFilePath));
450                     errors->prepend(error);
451                 }
452                 return false;
453             }
454         }
455     }
456
457     if (components)
458         *components = qmldirParser->components();
459     if (scripts)
460         *scripts = qmldirParser->scripts();
461
462     return true;
463 }
464
465 QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
466 {
467     QString dir = dir_arg;
468     if (dir.endsWith(QLatin1Char('/')) || dir.endsWith(QLatin1Char('\\')))
469         dir.chop(1);
470
471     QStringList paths = database->fileImportPath;
472     qSort(paths.begin(), paths.end(), greaterThan); // Ensure subdirs preceed their parents.
473
474     QString stableRelativePath = dir;
475     foreach(const QString &path, paths) {
476         if (dir.startsWith(path)) {
477             stableRelativePath = dir.mid(path.length()+1);
478             break;
479         }
480     }
481
482     stableRelativePath.replace(QLatin1Char('\\'), QLatin1Char('/'));
483
484     // remove optional versioning in dot notation from uri
485     int lastSlash = stableRelativePath.lastIndexOf(QLatin1Char('/'));
486     if (lastSlash >= 0) {
487         int versionDot = stableRelativePath.indexOf(QLatin1Char('.'), lastSlash);
488         if (versionDot >= 0)
489             stableRelativePath = stableRelativePath.left(versionDot);
490     }
491
492     stableRelativePath.replace(QLatin1Char('/'), QLatin1Char('.'));
493     return stableRelativePath;
494 }
495
496 bool QQmlImportsPrivate::add(const QQmlDirComponents &qmldircomponentsnetwork, 
497                                      const QString& uri_arg, const QString& prefix, int vmaj, int vmin, 
498                                      QQmlScript::Import::Type importType, 
499                                      QQmlImportDatabase *database, QList<QQmlError> *errors)
500 {
501     static QLatin1String Slash_qmldir("/qmldir");
502     static QLatin1Char Slash('/');
503
504     QQmlDirComponents qmldircomponents = qmldircomponentsnetwork;
505     QQmlDirScripts qmldirscripts;
506     QString uri = uri_arg;
507     QQmlImportedNamespace *s;
508     if (prefix.isEmpty()) {
509         s = &unqualifiedset;
510     } else {
511         s = set.value(prefix);
512         if (!s)
513             set.insert(prefix,(s=new QQmlImportedNamespace));
514     }
515     QString url = uri;
516     bool versionFound = false;
517     if (importType == QQmlScript::Import::Library) {
518
519         Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
520
521         url.replace(QLatin1Char('.'), Slash);
522         bool found = false;
523         QString dir;
524         QString qmldir;
525
526         // step 1: search for extension with fully encoded version number
527         foreach (const QString &p, database->fileImportPath) {
528             dir = p+Slash+url;
529
530             QFileInfo fi(dir+QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin)+QLatin1String("/qmldir"));
531             const QString absoluteFilePath = fi.absoluteFilePath();
532
533             if (fi.isFile()) {
534                 found = true;
535
536                 const QString absolutePath = fi.absolutePath();
537                 if (absolutePath.at(0) == QLatin1Char(':'))
538                     url = QLatin1String("qrc://") + absolutePath.mid(1);
539                 else
540                     url = QUrl::fromLocalFile(fi.absolutePath()).toString();
541                 uri = resolvedUri(dir, database);
542                 if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
543                     return false;
544                 break;
545             }
546         }
547
548         // TODO: Should this search be omitted if found == true?
549
550         // step 2: search for extension with encoded version major
551         foreach (const QString &p, database->fileImportPath) {
552             dir = p+Slash+url;
553
554             QFileInfo fi(dir+QString(QLatin1String(".%1")).arg(vmaj)+QLatin1String("/qmldir"));
555             const QString absoluteFilePath = fi.absoluteFilePath();
556
557             if (fi.isFile()) {
558                 found = true;
559
560                 const QString absolutePath = fi.absolutePath();
561                 if (absolutePath.at(0) == QLatin1Char(':'))
562                     url = QLatin1String("qrc://") + absolutePath.mid(1);
563                 else
564                     url = QUrl::fromLocalFile(fi.absolutePath()).toString();
565                 uri = resolvedUri(dir, database);
566                 if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
567                     return false;
568                 break;
569             }
570         }
571
572         if (!found) {
573             // step 3: search for extension without version number
574
575             foreach (const QString &p, database->fileImportPath) {
576                 dir = p+Slash+url;
577                 qmldir = dir+Slash_qmldir;
578
579                 QString absoluteFilePath = typeLoader->absoluteFilePath(qmldir);
580                 if (!absoluteFilePath.isEmpty()) {
581                     found = true;
582                     QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1);
583                     if (absolutePath.at(0) == QLatin1Char(':'))
584                         url = QLatin1String("qrc://") + absolutePath.mid(1);
585                     else
586                         url = QUrl::fromLocalFile(absolutePath).toString();
587                     uri = resolvedUri(dir, database);
588                     if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
589                         return false;
590                     break;
591                 }
592             }
593         }
594
595         if (QQmlMetaType::isModule(uri, vmaj, vmin))
596             versionFound = true;
597
598         if (!versionFound && qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) {
599             if (errors) {
600                 QQmlError error; // we don't set the url or line or column as these will be set by the loader.
601                 if (QQmlMetaType::isAnyModule(uri))
602                     error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin));
603                 else
604                     error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri_arg));
605                 errors->prepend(error);
606             }
607             return false;
608         }
609     } else {
610         if (importType == QQmlScript::Import::File && qmldircomponents.isEmpty()) {
611             QString importUrl = resolveLocalUrl(base, uri + Slash_qmldir);
612             QString localFileOrQrc = QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl);
613             if (!localFileOrQrc.isEmpty()) {
614                 QString dir = QQmlEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
615                 if (!typeLoader->directoryExists(dir)) {
616                     if (errors) {
617                         QQmlError error; // we don't set the line or column as these will be set by the loader.
618                         error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri_arg));
619                         error.setUrl(QUrl(importUrl));
620                         errors->prepend(error);
621                     }
622                     return false; // local import dirs must exist
623                 }
624                 uri = resolvedUri(dir, database);
625                 if (uri.endsWith(Slash))
626                     uri.chop(1);
627                 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
628                     if (!importExtension(localFileOrQrc,uri,database,&qmldircomponents,&qmldirscripts,errors))
629                         return false;
630                 }
631             } else {
632                 if (prefix.isEmpty()) {
633                     // directory must at least exist for valid import
634                     QString localFileOrQrc = QQmlEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
635                     if (!typeLoader->directoryExists(localFileOrQrc)) {
636                         if (errors) {
637                             QQmlError error; // we don't set the line or column as these will be set by the loader.
638                             if (localFileOrQrc.isEmpty())
639                                 error.setDescription(QQmlImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(uri));
640                             else
641                                 error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri));
642                             error.setUrl(QUrl(importUrl));
643                             errors->prepend(error);
644                         }
645                         return false;
646                     }
647                 }
648             }
649         }
650
651         url = resolveLocalUrl(base, url);
652     }
653
654     if (!versionFound && (vmaj > -1) && (vmin > -1) && !qmldircomponents.isEmpty()) {
655         int lowest_min = INT_MAX;
656         int highest_min = INT_MIN;
657
658         QList<QQmlDirParser::Component>::const_iterator cend = qmldircomponents.constEnd();
659         for (QList<QQmlDirParser::Component>::const_iterator cit = qmldircomponents.constBegin(); cit != cend; ++cit) {
660             if (cit->majorVersion == vmaj) {
661                 lowest_min = qMin(lowest_min, cit->minorVersion);
662                 highest_min = qMax(highest_min, cit->minorVersion);
663             }
664         }
665
666         if (lowest_min > vmin || highest_min < vmin) {
667             if (errors) {
668                 QQmlError error; // we don't set the url or line or column information, as these will be set by the loader.
669                 error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin));
670                 errors->prepend(error);
671             }
672             return false;
673         }
674     }
675
676     if (!url.endsWith(Slash))
677         url += Slash;
678
679     QMap<QString, QQmlDirParser::Script> scripts;
680
681     if (!qmldirscripts.isEmpty()) {
682         // Verify that we haven't imported these scripts already
683         QList<QQmlImportedNamespace::Data>::const_iterator end = s->imports.constEnd();
684         for (QList<QQmlImportedNamespace::Data>::const_iterator it = s->imports.constBegin(); it != end; ++it) {
685             if (it->uri == uri) {
686                 QQmlError error;
687                 error.setDescription(QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url));
688                 errors->prepend(error);
689                 return false;
690             }
691         }
692
693         QList<QQmlDirParser::Script>::const_iterator send = qmldirscripts.constEnd();
694         for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin(); sit != send; ++sit) {
695             // Only include scripts that match our requested version
696             if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
697                 ((vmin == -1) || (sit->minorVersion <= vmin))) {
698
699                 // Load the highest version that matches
700                 QMap<QString, QQmlDirParser::Script>::iterator it = scripts.find(sit->nameSpace);
701                 if (it == scripts.end() || (it->minorVersion < sit->minorVersion)) {
702                     scripts.insert(sit->nameSpace, *sit);
703                 }
704             }
705         }
706     }
707
708     QQmlImportedNamespace::Data data;
709     data.uri = uri;
710     data.url = url;
711     data.majversion = vmaj;
712     data.minversion = vmin;
713     data.isLibrary = importType == QQmlScript::Import::Library;
714     data.qmlDirComponents = qmldircomponents;
715     data.qmlDirScripts = scripts.values();
716
717     s->imports.prepend(data);
718
719     return true;
720 }
721
722 bool QQmlImportsPrivate::find(const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
723                                       QString* url_return, QList<QQmlError> *errors)
724 {
725     QQmlImportedNamespace *s = 0;
726     int slash = type.indexOf(QLatin1Char('/'));
727     if (slash >= 0) {
728         QString namespaceName = type.left(slash);
729         s = set.value(namespaceName);
730         if (!s) {
731             if (errors) {
732                 QQmlError error;
733                 error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName));
734                 errors->prepend(error);
735             }
736             return false;
737         }
738         int nslash = type.indexOf(QLatin1Char('/'),slash+1);
739         if (nslash > 0) {
740             if (errors) {
741                 QQmlError error;
742                 error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
743                 errors->prepend(error);
744             }
745             return false;
746         }
747     } else {
748         s = &unqualifiedset;
749     }
750     QString unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower)
751     if (s) {
752         if (s->find(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors))
753             return true;
754         if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) {
755             // qualified, and only 1 url
756             *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype + QLatin1String(".qml"));
757             return true;
758         }
759     }
760
761     return false;
762 }
763
764 QQmlImportedNamespace *QQmlImportsPrivate::findNamespace(const QString& type)
765 {
766     return set.value(type);
767 }
768
769 bool QQmlImportedNamespace::find(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
770           QString* url_return, QString *base, QList<QQmlError> *errors)
771 {
772     bool typeRecursionDetected = false;
773     for (int i=0; i<imports.count(); ++i) {
774         if (find_helper(typeLoader, imports.at(i), type, vmajor, vminor, type_return, url_return, base, &typeRecursionDetected)) {
775             if (qmlCheckTypes()) {
776                 // check for type clashes
777                 for (int j = i+1; j<imports.count(); ++j) {
778                     if (find_helper(typeLoader, imports.at(j), type, vmajor, vminor, 0, 0, base)) {
779                         if (errors) {
780                             QString u1 = imports.at(i).url;
781                             QString u2 = imports.at(j).url;
782                             if (base) {
783                                 QString b = *base;
784                                 int slash = b.lastIndexOf(QLatin1Char('/'));
785                                 if (slash >= 0) {
786                                     b = b.left(slash+1);
787                                     QString l = b.left(slash);
788                                     if (u1.startsWith(b))
789                                         u1 = u1.mid(b.count());
790                                     else if (u1 == l)
791                                         u1 = QQmlImportDatabase::tr("local directory");
792                                     if (u2.startsWith(b))
793                                         u2 = u2.mid(b.count());
794                                     else if (u2 == l)
795                                         u2 = QQmlImportDatabase::tr("local directory");
796                                 }
797                             }
798
799                             QQmlError error;
800                             if (u1 != u2) {
801                                 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
802                             } else {
803                                 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
804                                                         .arg(u1)
805                                                         .arg(imports.at(i).majversion).arg(imports.at(i).minversion)
806                                                         .arg(imports.at(j).majversion).arg(imports.at(j).minversion));
807                             }
808                             errors->prepend(error);
809                         }
810                         return false;
811                     }
812                 }
813             }
814             return true;
815         }
816     }
817     if (errors) {
818         QQmlError error;
819         if (typeRecursionDetected)
820             error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
821         else
822             error.setDescription(QQmlImportDatabase::tr("is not a type"));
823         errors->prepend(error);
824     }
825     return false;
826 }
827
828 /*!
829 \class QQmlImportDatabase
830 \brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
831 \internal
832 */
833 QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
834 : engine(e)
835 {
836     filePluginPath << QLatin1String(".");
837
838     // Search order is applicationDirPath(), $QML_IMPORT_PATH, QLibraryInfo::ImportsPath
839
840 #ifndef QT_NO_SETTINGS
841     QString installImportsPath =  QLibraryInfo::location(QLibraryInfo::ImportsPath);
842     addImportPath(installImportsPath);
843 #endif // QT_NO_SETTINGS
844
845     // env import paths
846     QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
847     if (!envImportPath.isEmpty()) {
848 #if defined(Q_OS_WIN)
849         QLatin1Char pathSep(';');
850 #else
851         QLatin1Char pathSep(':');
852 #endif
853         QStringList paths = QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts);
854         for (int ii = paths.count() - 1; ii >= 0; --ii)
855             addImportPath(paths.at(ii));
856     }
857
858     addImportPath(QCoreApplication::applicationDirPath());
859 }
860
861 QQmlImportDatabase::~QQmlImportDatabase()
862 {
863 }
864
865 /*!
866   \internal
867
868   Adds information to \a imports such that subsequent calls to resolveType()
869   will resolve types qualified by \a prefix by considering types found at the given \a uri.
870
871   The uri is either a directory (if importType is FileImport), or a URI resolved using paths
872   added via addImportPath() (if importType is LibraryImport).
873
874   The \a prefix may be empty, in which case the import location is considered for
875   unqualified types.
876
877   The base URL must already have been set with Import::setBaseUrl().
878 */
879 bool QQmlImports::addImport(QQmlImportDatabase *importDb, 
880                                     const QString& uri, const QString& prefix, int vmaj, int vmin, 
881                                     QQmlScript::Import::Type importType, 
882                                     const QQmlDirComponents &qmldircomponentsnetwork, 
883                                     QList<QQmlError> *errors)
884 {
885     if (qmlImportTrace())
886         qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: " 
887                            << uri << " " << vmaj << '.' << vmin << " " 
888                            << (importType==QQmlScript::Import::Library? "Library" : "File") 
889                            << " as " << prefix;
890
891     return d->add(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, importDb, errors);
892 }
893
894 /*!
895   \internal
896
897   Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
898   The \a prefix must contain the dot.
899
900   \a qmldirPath is the location of the qmldir file.
901  */
902 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
903                                                   const QString &qmldirPath, const QString &qmldirPluginPath,
904                                                   const QString &baseName, const QStringList &suffixes,
905                                                   const QString &prefix)
906 {
907     QStringList searchPaths = filePluginPath;
908     bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
909     if (!qmldirPluginPathIsRelative)
910         searchPaths.prepend(qmldirPluginPath);
911
912     foreach (const QString &pluginPath, searchPaths) {
913
914         QString resolvedPath;
915         if (pluginPath == QLatin1String(".")) {
916             if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
917                 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + qmldirPluginPath);
918             else
919                 resolvedPath = qmldirPath;
920         } else {
921             if (QDir::isRelativePath(pluginPath))
922                 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + pluginPath);
923             else
924                 resolvedPath = pluginPath;
925         }
926
927         // hack for resources, should probably go away
928         if (resolvedPath.startsWith(QLatin1Char(':')))
929             resolvedPath = QCoreApplication::applicationDirPath();
930
931         if (!resolvedPath.endsWith(QLatin1Char('/')))
932             resolvedPath += QLatin1Char('/');
933
934         foreach (const QString &suffix, suffixes) {
935             QString pluginFileName = prefix;
936
937             pluginFileName += baseName;
938             pluginFileName += suffix;
939
940             QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + pluginFileName);
941             if (!absolutePath.isEmpty())
942                 return absolutePath;
943         }
944     }
945
946     if (qmlImportTrace())
947         qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName 
948                  << "in" << qmldirPath;
949
950     return QString();
951 }
952
953 /*!
954   \internal
955
956   Returns the result of the merge of \a baseName with \a dir and the platform suffix.
957
958   \table
959   \header \li Platform \li Valid suffixes
960   \row \li Windows     \li \c .dll
961   \row \li Unix/Linux  \li \c .so
962   \row \li AIX  \li \c .a
963   \row \li HP-UX       \li \c .sl, \c .so (HP-UXi)
964   \row \li Mac OS X    \li \c .dylib, \c .bundle, \c .so
965   \endtable
966
967   Version number on unix are ignored.
968 */
969 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
970                                                   const QString &qmldirPath, const QString &qmldirPluginPath,
971                                                   const QString &baseName)
972 {
973 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
974     return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
975                          QStringList()
976 # ifdef QT_DEBUG
977                          << QLatin1String("d.dll") // try a qmake-style debug build first
978 # endif
979                          << QLatin1String(".dll"));
980 #else
981
982 # if defined(Q_OS_DARWIN)
983
984     return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
985                          QStringList()
986 # ifdef QT_DEBUG
987                          << QLatin1String("_debug.dylib") // try a qmake-style debug build first
988                          << QLatin1String(".dylib")
989 # else
990                          << QLatin1String(".dylib")
991                          << QLatin1String("_debug.dylib") // try a qmake-style debug build after
992 # endif
993                          << QLatin1String(".so")
994                          << QLatin1String(".bundle"),
995                          QLatin1String("lib"));
996 # else  // Generic Unix
997     QStringList validSuffixList;
998
999 #  if defined(Q_OS_HPUX)
1000 /*
1001     See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
1002     "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
1003     the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
1004  */
1005     validSuffixList << QLatin1String(".sl");
1006 #   if defined __ia64
1007     validSuffixList << QLatin1String(".so");
1008 #   endif
1009 #  elif defined(Q_OS_AIX)
1010     validSuffixList << QLatin1String(".a") << QLatin1String(".so");
1011 #  elif defined(Q_OS_UNIX)
1012     validSuffixList << QLatin1String(".so");
1013 #  endif
1014
1015     // Examples of valid library names:
1016     //  libfoo.so
1017
1018     return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
1019 # endif
1020
1021 #endif
1022 }
1023
1024 /*!
1025     \internal
1026 */
1027 QStringList QQmlImportDatabase::pluginPathList() const
1028 {
1029     return filePluginPath;
1030 }
1031
1032 /*!
1033     \internal
1034 */
1035 void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
1036 {
1037     filePluginPath = paths;
1038 }
1039
1040 /*!
1041     \internal
1042 */
1043 void QQmlImportDatabase::addPluginPath(const QString& path)
1044 {
1045     if (qmlImportTrace())
1046         qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
1047
1048     QUrl url = QUrl(path);
1049     if (url.isRelative() || url.scheme() == QLatin1String("file")
1050             || (url.scheme().length() == 1 && QFile::exists(path)) ) {  // windows path
1051         QDir dir = QDir(path);
1052         filePluginPath.prepend(dir.canonicalPath());
1053     } else {
1054         filePluginPath.prepend(path);
1055     }
1056 }
1057
1058 /*!
1059     \internal
1060 */
1061 void QQmlImportDatabase::addImportPath(const QString& path)
1062 {
1063     if (qmlImportTrace())
1064         qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
1065
1066     if (path.isEmpty())
1067         return;
1068
1069     QUrl url = QUrl(path);
1070     QString cPath;
1071
1072     if (url.isRelative() || url.scheme() == QLatin1String("file")
1073             || (url.scheme().length() == 1 && QFile::exists(path)) ) {  // windows path
1074         QDir dir = QDir(path);
1075         cPath = dir.canonicalPath();
1076     } else {
1077         cPath = path;
1078         cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
1079     }
1080
1081     if (!cPath.isEmpty()
1082         && !fileImportPath.contains(cPath))
1083         fileImportPath.prepend(cPath);
1084 }
1085
1086 /*!
1087     \internal
1088 */
1089 QStringList QQmlImportDatabase::importPathList() const
1090 {
1091     return fileImportPath;
1092 }
1093
1094 /*!
1095     \internal
1096 */
1097 void QQmlImportDatabase::setImportPathList(const QStringList &paths)
1098 {
1099     fileImportPath = paths;
1100 }
1101
1102 /*!
1103     \internal
1104 */
1105 bool QQmlImportDatabase::importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors)
1106 {
1107     if (qmlImportTrace())
1108         qDebug().nospace() << "QQmlImportDatabase::importPlugin: " << uri << " from " << filePath;
1109
1110 #ifndef QT_NO_LIBRARY
1111     QFileInfo fileInfo(filePath);
1112     const QString absoluteFilePath = fileInfo.absoluteFilePath();
1113
1114     bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
1115     bool typesRegistered = qmlEnginePluginsWithRegisteredTypes()->contains(absoluteFilePath);
1116
1117     if (typesRegistered) {
1118         Q_ASSERT_X(qmlEnginePluginsWithRegisteredTypes()->value(absoluteFilePath) == uri,
1119                    "QQmlImportDatabase::importExtension",
1120                    "Internal error: Plugin imported previously with different uri");
1121     }
1122
1123     if (!engineInitialized || !typesRegistered) {
1124         if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
1125             if (errors) {
1126                 QQmlError error;
1127                 error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
1128                 errors->prepend(error);
1129             }
1130             return false;
1131         }
1132         QPluginLoader loader(absoluteFilePath);
1133
1134         if (!loader.load()) {
1135             if (errors) {
1136                 QQmlError error;
1137                 error.setDescription(loader.errorString());
1138                 errors->prepend(error);
1139             }
1140             return false;
1141         }
1142
1143         QObject *instance = loader.instance();
1144         if (QQmlTypesExtensionInterface *iface = qobject_cast<QQmlExtensionInterface *>(instance)) {
1145
1146             const QByteArray bytes = uri.toUtf8();
1147             const char *moduleId = bytes.constData();
1148             if (!typesRegistered) {
1149
1150                 // XXX thread this code should probably be protected with a mutex.
1151                 qmlEnginePluginsWithRegisteredTypes()->insert(absoluteFilePath, uri);
1152                 iface->registerTypes(moduleId);
1153             }
1154             if (!engineInitialized) {
1155                 // things on the engine (eg. adding new global objects) have to be done for every 
1156                 // engine.  
1157                 // XXX protect against double initialization
1158                 initializedPlugins.insert(absoluteFilePath);
1159
1160                 QQmlExtensionInterface *eiface = 
1161                     qobject_cast<QQmlExtensionInterface *>(instance);
1162                 if (eiface) {
1163                     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
1164                     ep->typeLoader.initializeEngine(eiface, moduleId);
1165                 }
1166             }
1167         } else {
1168             if (errors) {
1169                 QQmlError error;
1170                 error.setDescription(loader.errorString());
1171                 errors->prepend(error);
1172             }
1173             return false;
1174         }
1175     }
1176
1177     return true;
1178 #else
1179     return false;
1180 #endif
1181 }
1182
1183 QT_END_NAMESPACE