Merge remote-tracking branch 'origin/master' into api_changes
[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     QString 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 QString 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 QString();
544                 break;
545             }
546         }
547
548         if (!found) {
549             // step 2: search for extension with encoded version major
550             foreach (const QString &p, database->fileImportPath) {
551                 dir = p+Slash+url;
552
553                 QFileInfo fi(dir+QString(QLatin1String(".%1")).arg(vmaj)+QLatin1String("/qmldir"));
554                 const QString absoluteFilePath = fi.absoluteFilePath();
555
556                 if (fi.isFile()) {
557                     found = true;
558
559                     const QString absolutePath = fi.absolutePath();
560                     if (absolutePath.at(0) == QLatin1Char(':'))
561                         url = QLatin1String("qrc://") + absolutePath.mid(1);
562                     else
563                         url = QUrl::fromLocalFile(fi.absolutePath()).toString();
564                     uri = resolvedUri(dir, database);
565                     if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
566                         return QString();
567                     break;
568                 }
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 QString();
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 QString();
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 QString(); // 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 QString();
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 QString();
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 QString();
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 QString();
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 data.url;
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   Returns the resolved URL of the import on success.
878
879   The base URL must already have been set with Import::setBaseUrl().
880 */
881 QString QQmlImports::addImport(QQmlImportDatabase *importDb,
882                                     const QString& uri, const QString& prefix, int vmaj, int vmin, 
883                                     QQmlScript::Import::Type importType, 
884                                     const QQmlDirComponents &qmldircomponentsnetwork, 
885                                     QList<QQmlError> *errors)
886 {
887     if (qmlImportTrace())
888         qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: " 
889                            << uri << " " << vmaj << '.' << vmin << " " 
890                            << (importType==QQmlScript::Import::Library? "Library" : "File") 
891                            << " as " << prefix;
892
893     return d->add(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, importDb, errors);
894 }
895
896 /*!
897   \internal
898
899   Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
900   The \a prefix must contain the dot.
901
902   \a qmldirPath is the location of the qmldir file.
903  */
904 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
905                                                   const QString &qmldirPath, const QString &qmldirPluginPath,
906                                                   const QString &baseName, const QStringList &suffixes,
907                                                   const QString &prefix)
908 {
909     QStringList searchPaths = filePluginPath;
910     bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
911     if (!qmldirPluginPathIsRelative)
912         searchPaths.prepend(qmldirPluginPath);
913
914     foreach (const QString &pluginPath, searchPaths) {
915
916         QString resolvedPath;
917         if (pluginPath == QLatin1String(".")) {
918             if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
919                 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + qmldirPluginPath);
920             else
921                 resolvedPath = qmldirPath;
922         } else {
923             if (QDir::isRelativePath(pluginPath))
924                 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + pluginPath);
925             else
926                 resolvedPath = pluginPath;
927         }
928
929         // hack for resources, should probably go away
930         if (resolvedPath.startsWith(QLatin1Char(':')))
931             resolvedPath = QCoreApplication::applicationDirPath();
932
933         if (!resolvedPath.endsWith(QLatin1Char('/')))
934             resolvedPath += QLatin1Char('/');
935
936         foreach (const QString &suffix, suffixes) {
937             QString pluginFileName = prefix;
938
939             pluginFileName += baseName;
940             pluginFileName += suffix;
941
942             QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + pluginFileName);
943             if (!absolutePath.isEmpty())
944                 return absolutePath;
945         }
946     }
947
948     if (qmlImportTrace())
949         qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName 
950                  << "in" << qmldirPath;
951
952     return QString();
953 }
954
955 /*!
956   \internal
957
958   Returns the result of the merge of \a baseName with \a dir and the platform suffix.
959
960   \table
961   \header \li Platform \li Valid suffixes
962   \row \li Windows     \li \c .dll
963   \row \li Unix/Linux  \li \c .so
964   \row \li AIX  \li \c .a
965   \row \li HP-UX       \li \c .sl, \c .so (HP-UXi)
966   \row \li Mac OS X    \li \c .dylib, \c .bundle, \c .so
967   \endtable
968
969   Version number on unix are ignored.
970 */
971 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
972                                                   const QString &qmldirPath, const QString &qmldirPluginPath,
973                                                   const QString &baseName)
974 {
975 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
976     return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
977                          QStringList()
978 # ifdef QT_DEBUG
979                          << QLatin1String("d.dll") // try a qmake-style debug build first
980 # endif
981                          << QLatin1String(".dll"));
982 #else
983
984 # if defined(Q_OS_DARWIN)
985
986     return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
987                          QStringList()
988 # ifdef QT_DEBUG
989                          << QLatin1String("_debug.dylib") // try a qmake-style debug build first
990                          << QLatin1String(".dylib")
991 # else
992                          << QLatin1String(".dylib")
993                          << QLatin1String("_debug.dylib") // try a qmake-style debug build after
994 # endif
995                          << QLatin1String(".so")
996                          << QLatin1String(".bundle"),
997                          QLatin1String("lib"));
998 # else  // Generic Unix
999     QStringList validSuffixList;
1000
1001 #  if defined(Q_OS_HPUX)
1002 /*
1003     See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
1004     "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
1005     the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
1006  */
1007     validSuffixList << QLatin1String(".sl");
1008 #   if defined __ia64
1009     validSuffixList << QLatin1String(".so");
1010 #   endif
1011 #  elif defined(Q_OS_AIX)
1012     validSuffixList << QLatin1String(".a") << QLatin1String(".so");
1013 #  elif defined(Q_OS_UNIX)
1014     validSuffixList << QLatin1String(".so");
1015 #  endif
1016
1017     // Examples of valid library names:
1018     //  libfoo.so
1019
1020     return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
1021 # endif
1022
1023 #endif
1024 }
1025
1026 /*!
1027     \internal
1028 */
1029 QStringList QQmlImportDatabase::pluginPathList() const
1030 {
1031     return filePluginPath;
1032 }
1033
1034 /*!
1035     \internal
1036 */
1037 void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
1038 {
1039     filePluginPath = paths;
1040 }
1041
1042 /*!
1043     \internal
1044 */
1045 void QQmlImportDatabase::addPluginPath(const QString& path)
1046 {
1047     if (qmlImportTrace())
1048         qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
1049
1050     QUrl url = QUrl(path);
1051     if (url.isRelative() || url.scheme() == QLatin1String("file")
1052             || (url.scheme().length() == 1 && QFile::exists(path)) ) {  // windows path
1053         QDir dir = QDir(path);
1054         filePluginPath.prepend(dir.canonicalPath());
1055     } else {
1056         filePluginPath.prepend(path);
1057     }
1058 }
1059
1060 /*!
1061     \internal
1062 */
1063 void QQmlImportDatabase::addImportPath(const QString& path)
1064 {
1065     if (qmlImportTrace())
1066         qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
1067
1068     if (path.isEmpty())
1069         return;
1070
1071     QUrl url = QUrl(path);
1072     QString cPath;
1073
1074     if (url.isRelative() || url.scheme() == QLatin1String("file")
1075             || (url.scheme().length() == 1 && QFile::exists(path)) ) {  // windows path
1076         QDir dir = QDir(path);
1077         cPath = dir.canonicalPath();
1078     } else {
1079         cPath = path;
1080         cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
1081     }
1082
1083     if (!cPath.isEmpty()
1084         && !fileImportPath.contains(cPath))
1085         fileImportPath.prepend(cPath);
1086 }
1087
1088 /*!
1089     \internal
1090 */
1091 QStringList QQmlImportDatabase::importPathList() const
1092 {
1093     return fileImportPath;
1094 }
1095
1096 /*!
1097     \internal
1098 */
1099 void QQmlImportDatabase::setImportPathList(const QStringList &paths)
1100 {
1101     fileImportPath = paths;
1102 }
1103
1104 /*!
1105     \internal
1106 */
1107 bool QQmlImportDatabase::importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors)
1108 {
1109     if (qmlImportTrace())
1110         qDebug().nospace() << "QQmlImportDatabase::importPlugin: " << uri << " from " << filePath;
1111
1112 #ifndef QT_NO_LIBRARY
1113     QFileInfo fileInfo(filePath);
1114     const QString absoluteFilePath = fileInfo.absoluteFilePath();
1115
1116     bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
1117     bool typesRegistered = qmlEnginePluginsWithRegisteredTypes()->contains(absoluteFilePath);
1118
1119     if (typesRegistered) {
1120         Q_ASSERT_X(qmlEnginePluginsWithRegisteredTypes()->value(absoluteFilePath) == uri,
1121                    "QQmlImportDatabase::importExtension",
1122                    "Internal error: Plugin imported previously with different uri");
1123     }
1124
1125     if (!engineInitialized || !typesRegistered) {
1126         if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
1127             if (errors) {
1128                 QQmlError error;
1129                 error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
1130                 errors->prepend(error);
1131             }
1132             return false;
1133         }
1134         QPluginLoader loader(absoluteFilePath);
1135
1136         if (!loader.load()) {
1137             if (errors) {
1138                 QQmlError error;
1139                 error.setDescription(loader.errorString());
1140                 errors->prepend(error);
1141             }
1142             return false;
1143         }
1144
1145         QObject *instance = loader.instance();
1146         if (QQmlTypesExtensionInterface *iface = qobject_cast<QQmlExtensionInterface *>(instance)) {
1147
1148             const QByteArray bytes = uri.toUtf8();
1149             const char *moduleId = bytes.constData();
1150             if (!typesRegistered) {
1151
1152                 // XXX thread this code should probably be protected with a mutex.
1153                 qmlEnginePluginsWithRegisteredTypes()->insert(absoluteFilePath, uri);
1154                 iface->registerTypes(moduleId);
1155             }
1156             if (!engineInitialized) {
1157                 // things on the engine (eg. adding new global objects) have to be done for every 
1158                 // engine.  
1159                 // XXX protect against double initialization
1160                 initializedPlugins.insert(absoluteFilePath);
1161
1162                 QQmlExtensionInterface *eiface = 
1163                     qobject_cast<QQmlExtensionInterface *>(instance);
1164                 if (eiface) {
1165                     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
1166                     ep->typeLoader.initializeEngine(eiface, moduleId);
1167                 }
1168             }
1169         } else {
1170             if (errors) {
1171                 QQmlError error;
1172                 error.setDescription(loader.errorString());
1173                 errors->prepend(error);
1174             }
1175             return false;
1176         }
1177     }
1178
1179     return true;
1180 #else
1181     return false;
1182 #endif
1183 }
1184
1185 QT_END_NAMESPACE