Initial bundle support
authorAaron Kennedy <aaron.kennedy@nokia.com>
Tue, 10 Apr 2012 17:11:30 +0000 (18:11 +0100)
committerQt by Nokia <qt-info@nokia.com>
Fri, 4 May 2012 11:15:01 +0000 (13:15 +0200)
Change-Id: I095249f64ecf4ef1e3fbfb164e3d50edffab61e8
Reviewed-by: Roberto Raggi <roberto.raggi@nokia.com>
52 files changed:
src/imports/folderlistmodel/qquickfolderlistmodel.cpp
src/qml/qml/parser/parser.pri
src/qml/qml/qml.pri
src/qml/qml/qqmlbundle.cpp [new file with mode: 0644]
src/qml/qml/qqmlbundle_p.h [new file with mode: 0644]
src/qml/qml/qqmlcompiler.cpp
src/qml/qml/qqmlcompiler_p.h
src/qml/qml/qqmldirparser.cpp
src/qml/qml/qqmldirparser_p.h
src/qml/qml/qqmlengine.cpp
src/qml/qml/qqmlengine.h
src/qml/qml/qqmlengine_p.h
src/qml/qml/qqmlfile.cpp [new file with mode: 0644]
src/qml/qml/qqmlfile.h [new file with mode: 0644]
src/qml/qml/qqmlimport.cpp
src/qml/qml/qqmlimport_p.h
src/qml/qml/qqmlscript.cpp
src/qml/qml/qqmlscript_p.h
src/qml/qml/qqmltypeloader.cpp
src/qml/qml/qqmltypeloader_p.h
src/qml/qml/qquickworkerscript.cpp
src/qml/qml/v8/qv8include.cpp
src/quick/items/qquickanimatedimage.cpp
src/quick/items/qquickborderimage.cpp
src/quick/util/qquickfontloader.cpp
src/quick/util/qquickpixmapcache.cpp
tests/auto/qml/qml.pro
tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.1.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.2.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/MyType.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/subdir/MySubType.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/componentFromBundle/bundledata/test.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/import.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/qmldir [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/subdir/qmldir [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/imports/bundletest/empty.json [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin.cpp [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin1.pro [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/qmldir [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/st.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/test.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/MyType.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/test.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/MyType.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/test.qml [new file with mode: 0644]
tests/auto/qml/qqmlbundle/qqmlbundle.pro [new file with mode: 0644]
tests/auto/qml/qqmlbundle/tst_qqmlbundle.cpp [new file with mode: 0644]
tests/auto/qml/qqmlbundle/tst_qqmlbundle.pro [new file with mode: 0644]
tools/qmlbundle/main.cpp [new file with mode: 0644]
tools/qmlbundle/qmlbundle.pro [new file with mode: 0644]
tools/qmlscene/main.cpp
tools/tools.pro

index b937fbd..218d795 100644 (file)
@@ -44,7 +44,7 @@
 #include "fileinfothread_p.h"
 #include "fileproperty_p.h"
 #include <qqmlcontext.h>
-#include <private/qqmlengine_p.h>
+#include <qqmlfile.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -369,7 +369,7 @@ void QQuickFolderListModel::setFolder(const QUrl &folder)
     if (folder == d->currentDir)
         return;
 
-    QString localPath = QQmlEnginePrivate::urlToLocalFileOrQrc(folder);
+    QString localPath = QQmlFile::urlToLocalFileOrQrc(folder);
     QString resolvedPath = QDir::cleanPath(QUrl(localPath).path());
 
     beginResetModel();
@@ -412,7 +412,7 @@ void QQuickFolderListModel::setRootFolder(const QUrl &path)
     if (path.isEmpty())
         return;
 
-    QString localPath = QQmlEnginePrivate::urlToLocalFileOrQrc(path);
+    QString localPath = QQmlFile::urlToLocalFileOrQrc(path);
     QString resolvedPath = QDir::cleanPath(QUrl(localPath).path());
 
     QFileInfo info(resolvedPath);
index 6be85ba..7dba8bc 100644 (file)
@@ -8,7 +8,7 @@ HEADERS += \
     $$PWD/qqmljsmemorypool_p.h \
     $$PWD/qqmljsparser_p.h \
     $$PWD/qqmljsglobal_p.h \
-    $$PWD/qqmljskeywords_p.h
+    $$PWD/qqmljskeywords_p.h \
 
 SOURCES += \
     $$PWD/qqmljsast.cpp \
@@ -16,4 +16,4 @@ SOURCES += \
     $$PWD/qqmljsengine_p.cpp \
     $$PWD/qqmljsgrammar.cpp \
     $$PWD/qqmljslexer.cpp \
-    $$PWD/qqmljsparser.cpp
+    $$PWD/qqmljsparser.cpp \
index 1237740..30acea1 100644 (file)
@@ -51,6 +51,8 @@ SOURCES += \
     $$PWD/qqmlabstractbinding.cpp \
     $$PWD/qqmlvaluetypeproxybinding.cpp \
     $$PWD/qqmlglobal.cpp \
+    $$PWD/qqmlfile.cpp \
+    $$PWD/qqmlbundle.cpp \
 
 HEADERS += \
     $$PWD/qqmlglobal_p.h \
@@ -122,6 +124,8 @@ HEADERS += \
     $$PWD/qqmljavascriptexpression_p.h \
     $$PWD/qqmlabstractbinding_p.h \
     $$PWD/qqmlvaluetypeproxybinding_p.h \
+    $$PWD/qqmlfile.h \
+    $$PWD/qqmlbundle_p.h \
 
 include(parser/parser.pri)
 include(rewriter/rewriter.pri)
diff --git a/src/qml/qml/qqmlbundle.cpp b/src/qml/qml/qqmlbundle.cpp
new file mode 100644 (file)
index 0000000..fa47cb5
--- /dev/null
@@ -0,0 +1,319 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlbundle_p.h"
+#include <QtCore/QtCore>
+#include <iostream>
+#include <cstdlib>
+
+static const unsigned char qmlBundleHeaderData[] = { 255, 'q', 'm', 'l', 'd', 'i', 'r', 255 };
+static const unsigned int qmlBundleHeaderLength = 8;
+
+//
+// Entries
+//
+QString QQmlBundle::FileEntry::fileName() const
+{
+    return QString((QChar *)&data[0], fileNameLength / sizeof(QChar));
+}
+
+bool QQmlBundle::FileEntry::isFileName(const QString &fileName) const
+{
+    return fileName.length() * sizeof(QChar) == (unsigned)fileNameLength &&
+           0 == ::memcmp(fileName.constData(), &data[0], fileNameLength);
+}
+
+const char *QQmlBundle::FileEntry::contents() const {
+    return &data[fileNameLength];
+}
+
+quint64 QQmlBundle::FileEntry::fileSize() const
+{
+    return size - (sizeof(FileEntry) + fileNameLength);
+}
+
+
+//
+// QQmlBundle
+//
+QQmlBundle::QQmlBundle(const QString &fileName)
+: file(fileName),
+  buffer(0),
+  bufferSize(0),
+  opened(false),
+  headerWritten(false)
+{
+}
+
+QQmlBundle::~QQmlBundle()
+{
+    close();
+}
+
+bool QQmlBundle::open(QIODevice::OpenMode mode)
+{
+    if (!opened) {
+        if (!file.open(mode))
+            return false;
+
+        bufferSize = file.size();
+        buffer = file.map(0, bufferSize);
+
+        if (bufferSize == 0 ||
+            (bufferSize >= 8 && 0 == ::memcmp(buffer, qmlBundleHeaderData, qmlBundleHeaderLength))) {
+            opened = true;
+            headerWritten = false;
+            return true;
+        } else {
+            close();
+            return false;
+        }
+    }
+    return true;
+}
+
+void QQmlBundle::close()
+{
+    if (opened) {
+        opened = false;
+        headerWritten = false;
+        file.unmap(buffer);
+        file.close();
+    }
+}
+
+QList<const QQmlBundle::FileEntry *> QQmlBundle::files() const
+{
+    QList<const FileEntry *> files;
+    const char *ptr = (const char *) buffer + qmlBundleHeaderLength;
+    const char *end = (const char *) buffer + bufferSize;
+
+    while (ptr < end) {
+        const Entry *cmd = (const Entry *) ptr;
+
+        switch (static_cast<Entry::Kind>(cmd->kind)) {
+        case Entry::File: {
+            const FileEntry *f = reinterpret_cast<const FileEntry *>(cmd);
+            files.append(f);
+        }   break;
+
+        case Entry::Link:
+        case Entry::Skip: {
+            // Skip
+        }   break;
+
+        default:
+            // throw an error
+            return QList<const FileEntry *>();
+        } // switch
+
+        ptr += cmd->size;
+        Q_ASSERT(ptr <= end); // throw an error
+    }
+    return files;
+}
+
+void QQmlBundle::remove(const FileEntry *entry)
+{
+    Q_ASSERT(entry->kind == Entry::File); // ### throw an error
+    Q_ASSERT(file.isWritable());
+    const_cast<FileEntry *>(entry)->kind = Entry::Skip;
+}
+
+int QQmlBundle::bundleHeaderLength()
+{
+    return qmlBundleHeaderLength;
+}
+
+bool QQmlBundle::isBundleHeader(const char *data, int size)
+{
+    if ((unsigned int)size < qmlBundleHeaderLength)
+        return false;
+
+    return 0 == ::memcmp(data, qmlBundleHeaderData, qmlBundleHeaderLength);
+}
+
+//
+// find a some empty space we can use to insert new entries.
+//
+const QQmlBundle::Entry *QQmlBundle::findInsertPoint(quint64 size, qint64 *offset)
+{
+    const char *ptr = (const char *) buffer + qmlBundleHeaderLength;
+    const char *end = (const char *) buffer + bufferSize;
+
+    while (ptr < end) {
+        const Entry *cmd = (const Entry *) ptr;
+
+        if (cmd->kind == Entry::Skip && size + sizeof(RawEntry) < cmd->size) {
+            *offset = ptr - ((const char *) buffer + qmlBundleHeaderLength);
+            return cmd;
+        }
+
+        ptr += cmd->size;
+        Q_ASSERT(ptr <= end); // throw an error
+    }
+
+    return 0;
+}
+
+const QQmlBundle::FileEntry *QQmlBundle::find(const QString &fileName) const
+{
+    const char *ptr = (const char *) buffer + qmlBundleHeaderLength;
+    const char *end = (const char *) buffer + bufferSize;
+
+    while (ptr < end) {
+        const Entry *cmd = (const Entry *) ptr;
+
+        if (cmd->kind == Entry::File) {
+            const FileEntry *fileEntry = static_cast<const FileEntry *>(cmd);
+
+            if (fileEntry->isFileName(fileName))
+                return fileEntry;
+        }
+
+        ptr += cmd->size;
+        Q_ASSERT(ptr <= end); // throw an error
+    }
+
+    return 0;
+}
+
+const QQmlBundle::FileEntry *QQmlBundle::link(const FileEntry *entry, const QString &linkName) const
+{
+    const char *ptr = (const char *) buffer + entry->link;
+
+    while (ptr != (const char *)buffer) {
+        const Entry *cmd = (const Entry *) ptr;
+        Q_ASSERT(cmd->kind == Entry::Link);
+
+        const FileEntry *fileEntry = static_cast<const FileEntry *>(cmd);
+        if (fileEntry->fileName() == linkName)
+            return fileEntry;
+
+        ptr = (const char *) buffer + fileEntry->link;
+    }
+
+    return 0;
+}
+
+const QQmlBundle::FileEntry *QQmlBundle::find(const QChar *fileName, int length) const
+{
+    return find(QString::fromRawData(fileName, length));
+}
+
+bool QQmlBundle::add(const QString &name, const QString &fileName)
+{
+    if (!file.isWritable())
+        return false;
+    else if (find(fileName))
+        return false;
+
+    QFile inputFile(fileName);
+    if (!inputFile.open(QFile::ReadOnly))
+        return false;
+
+    // ### use best-fit algorithm
+    if (!file.atEnd())
+        file.seek(file.size());
+
+    FileEntry cmd;
+    const quint64 inputFileSize = inputFile.size();
+
+    cmd.kind = Entry::File;
+    cmd.link = 0;
+    cmd.size = sizeof(FileEntry) + name.length() * sizeof(QChar) + inputFileSize;
+    cmd.fileNameLength = name.length() * sizeof(QChar);
+
+    if (bufferSize == 0 && headerWritten == false) {
+        file.write((const char *)qmlBundleHeaderData, qmlBundleHeaderLength);
+        headerWritten = true;
+    }
+
+    file.write((const char *) &cmd, sizeof(FileEntry));
+    file.write((const char *) name.constData(), name.length() * sizeof(QChar));
+
+    uchar *source = inputFile.map(0, inputFileSize);
+    file.write((const char *) source, inputFileSize);
+    inputFile.unmap(source);
+    return true;
+}
+
+bool QQmlBundle::add(const QString &fileName)
+{
+    return add(fileName, fileName);
+}
+
+bool QQmlBundle::addMetaLink(const QString &fileName,
+                             const QString &linkName,
+                             const QByteArray &data)
+{
+    if (!file.isWritable())
+        return false;
+
+    const FileEntry *fileEntry = find(fileName);
+    if (!fileEntry)
+        return false;
+
+    // ### use best-fit algorithm
+    if (!file.atEnd())
+        file.seek(file.size());
+
+    FileEntry cmd;
+
+    const quint64 inputFileSize = data.size();
+
+    cmd.kind = Entry::Link;
+    cmd.link = fileEntry->link;
+    cmd.size = sizeof(FileEntry) + linkName.length() * sizeof(QChar) + inputFileSize;
+    cmd.fileNameLength = linkName.length() * sizeof(QChar);
+
+    if (bufferSize == 0 && headerWritten == false) {
+        file.write((const char *)qmlBundleHeaderData, qmlBundleHeaderLength);
+        headerWritten = true;
+    }
+
+    const_cast<FileEntry *>(fileEntry)->link = file.size();
+
+    file.write((const char *) &cmd, sizeof(FileEntry));
+    file.write((const char *) linkName.constData(), linkName.length() * sizeof(QChar));
+    file.write((const char *) data.constData(), inputFileSize);
+    return true;
+}
diff --git a/src/qml/qml/qqmlbundle_p.h b/src/qml/qml/qqmlbundle_p.h
new file mode 100644 (file)
index 0000000..029acf1
--- /dev/null
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLBUNDLE_P_H
+#define QQMLBUNDLE_P_H
+
+#include <QtCore/qfile.h>
+#include <QtCore/qstring.h>
+#include <QtQml/qtqmlglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QML_EXPORT QQmlBundle
+{
+    Q_DISABLE_COPY(QQmlBundle)
+public:
+    struct Q_PACKED Q_QML_EXPORT Entry
+    {
+        enum Kind {
+            File = 123, // Normal file
+            Skip,       // Empty space
+            Link        // A meta data linked file
+
+            // ### add entries for qmldir, index, ...
+        };
+
+        int kind;
+        quint64 size;
+    };
+
+    struct Q_PACKED Q_QML_EXPORT RawEntry : public Entry
+    {
+        char data[]; // trailing data
+    };
+
+    struct Q_PACKED Q_QML_EXPORT FileEntry : public Entry
+    {
+        quint64 link;
+        int fileNameLength;
+        char data[]; // trailing data
+
+        QString fileName() const;
+        bool isFileName(const QString &) const;
+
+        quint64 fileSize() const;
+        const char *contents() const;
+    };
+
+    QQmlBundle(const QString &fileName);
+    ~QQmlBundle();
+
+    bool open(QIODevice::OpenMode mode = QIODevice::ReadWrite);
+    void close();
+
+    QList<const FileEntry *> files() const;
+    void remove(const FileEntry *entry);
+    bool add(const QString &fileName);
+    bool add(const QString &name, const QString &fileName);
+
+    bool addMetaLink(const QString &fileName,
+                     const QString &linkName,
+                     const QByteArray &data);
+
+    const FileEntry *find(const QString &fileName) const;
+    const FileEntry *find(const QChar *fileName, int length) const;
+
+    const FileEntry *link(const FileEntry *, const QString &linkName) const;
+
+    static int bundleHeaderLength();
+    static bool isBundleHeader(const char *, int size);
+private:
+    const Entry *findInsertPoint(quint64 size, qint64 *offset);
+
+private:
+    QFile file;
+    uchar *buffer;
+    quint64 bufferSize;
+    bool opened:1;
+    bool headerWritten:1;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLBUNDLE_P_H
index 8c47c7c..5708a84 100644 (file)
@@ -1742,7 +1742,7 @@ bool QQmlCompiler::buildProperty(QQmlScript::Property *prop,
         }
 
         QQmlType *type = 0;
-        QQmlImportedNamespace *typeNamespace = 0;
+        QQmlImportNamespace *typeNamespace = 0;
         unit->imports().resolveType(prop->name().toString(), &type, 0, 0, 0, &typeNamespace);
 
         if (typeNamespace) {
@@ -1849,10 +1849,10 @@ bool QQmlCompiler::buildProperty(QQmlScript::Property *prop,
     return true;
 }
 
-bool QQmlCompiler::buildPropertyInNamespace(QQmlImportedNamespace *ns,
-                                                    QQmlScript::Property *nsProp, 
-                                                    QQmlScript::Object *obj, 
-                                                    const BindingContext &ctxt)
+bool QQmlCompiler::buildPropertyInNamespace(QQmlImportNamespace *ns,
+                                            QQmlScript::Property *nsProp,
+                                            QQmlScript::Object *obj,
+                                            const BindingContext &ctxt)
 {
     if (!nsProp->value)
         COMPILE_EXCEPTION(nsProp, tr("Invalid use of namespace"));
index 3b6fdf1..5a52dcf 100644 (file)
@@ -318,7 +318,7 @@ private:
                      const QQmlCompilerTypes::BindingContext &);
     bool buildProperty(QQmlScript::Property *prop, QQmlScript::Object *obj, 
                        const QQmlCompilerTypes::BindingContext &);
-    bool buildPropertyInNamespace(QQmlImportedNamespace *ns,
+    bool buildPropertyInNamespace(QQmlImportNamespace *ns,
                                   QQmlScript::Property *prop, 
                                   QQmlScript::Object *obj, 
                                   const QQmlCompilerTypes::BindingContext &);
index 8228931..2da4a33 100644 (file)
@@ -43,6 +43,7 @@
 #include "qqmlerror.h"
 #include "qqmlglobal_p.h"
 
+#include <QtQml/qqmlfile.h>
 #include <QtCore/QTextStream>
 #include <QtCore/QFile>
 #include <QtCore/QtDebug>
@@ -58,26 +59,6 @@ QQmlDirParser::~QQmlDirParser()
 {
 }
 
-QUrl QQmlDirParser::url() const
-{
-    return _url;
-}
-
-void QQmlDirParser::setUrl(const QUrl &url)
-{
-    _url = url;
-}
-
-QString QQmlDirParser::fileSource() const
-{
-    return _filePathSouce;
-}
-
-void QQmlDirParser::setFileSource(const QString &filePath)
-{
-    _filePathSouce = filePath;
-}
-
 QString QQmlDirParser::source() const
 {
     return _source;
@@ -94,6 +75,9 @@ bool QQmlDirParser::isParsed() const
     return _isParsed;
 }
 
+/*!
+\a url is used for generating errors.
+*/
 bool QQmlDirParser::parse()
 {
     if (_isParsed)
@@ -105,23 +89,6 @@ bool QQmlDirParser::parse()
     _components.clear();
     _scripts.clear();
 
-    if (_source.isEmpty() && !_filePathSouce.isEmpty()) {
-        QFile file(_filePathSouce);
-        if (!QQml_isFileCaseCorrect(_filePathSouce)) {
-            QQmlError error;
-            error.setDescription(QString::fromUtf8("cannot load module \"$$URI$$\": File name case mismatch for \"%1\"").arg(_filePathSouce));
-            _errors.prepend(error);
-            return false;
-        } else if (file.open(QFile::ReadOnly)) {
-            _source = QString::fromUtf8(file.readAll());
-        } else {
-            QQmlError error;
-            error.setDescription(QString::fromUtf8("module \"$$URI$$\" definition \"%1\" not readable").arg(_filePathSouce));
-            _errors.prepend(error);
-            return false;
-        }
-    }
-
     QTextStream stream(&_source);
     int lineNumber = 0;
 
@@ -246,7 +213,6 @@ bool QQmlDirParser::parse()
 void QQmlDirParser::reportError(int line, int column, const QString &description)
 {
     QQmlError error;
-    error.setUrl(_url);
     error.setLine(line);
     error.setColumn(column);
     error.setDescription(description);
@@ -261,6 +227,12 @@ bool QQmlDirParser::hasError() const
     return false;
 }
 
+void QQmlDirParser::setError(const QQmlError &e)
+{
+    _errors.clear();
+    _errors.append(e);
+}
+
 QList<QQmlError> QQmlDirParser::errors(const QString &uri) const
 {
     QList<QQmlError> errors = _errors;
index f46e178..77fe277 100644 (file)
@@ -60,6 +60,7 @@
 QT_BEGIN_NAMESPACE
 
 class QQmlError;
+class QQmlEngine;
 class QQmlDirParser
 {
     Q_DISABLE_COPY(QQmlDirParser)
@@ -68,12 +69,6 @@ public:
     QQmlDirParser();
     ~QQmlDirParser();
 
-    QUrl url() const;
-    void setUrl(const QUrl &url);
-
-    QString fileSource() const;
-    void setFileSource(const QString &filePath);
-
     QString source() const;
     void setSource(const QString &source);
 
@@ -81,6 +76,7 @@ public:
     bool parse();
 
     bool hasError() const;
+    void setError(const QQmlError &);
     QList<QQmlError> errors(const QString &uri) const;
 
     struct Plugin
@@ -146,9 +142,7 @@ private:
 
 private:
     QList<QQmlError> _errors;
-    QUrl _url;
     QString _source;
-    QString _filePathSouce;
     QList<Component> _components;
     QList<Script> _scripts;
     QList<Plugin> _plugins;
index 91c86a2..1c00269 100644 (file)
@@ -1303,44 +1303,6 @@ void QQmlData::setBindingBit(QObject *obj, int bit)
     bindingBits[bit / 32] |= (1 << (bit % 32));
 }
 
-QString QQmlEnginePrivate::urlToLocalFileOrQrc(const QUrl& url)
-{
-    if (url.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) == 0) {
-        if (url.authority().isEmpty())
-            return QLatin1Char(':') + url.path();
-        return QString();
-    }
-    return url.toLocalFile();
-}
-
-
-static QString toLocalFile(const QString &url)
-{
-    if (!url.startsWith(QLatin1String("file://"), Qt::CaseInsensitive))
-        return QString();
-
-    QString file = url.mid(7);
-
-    //XXX TODO: handle windows hostnames: "//servername/path/to/file.txt"
-
-    // magic for drives on windows
-    if (file.length() > 2 && file.at(0) == QLatin1Char('/') && file.at(2) == QLatin1Char(':'))
-        file.remove(0, 1);
-
-    return file;
-}
-
-QString QQmlEnginePrivate::urlToLocalFileOrQrc(const QString& url)
-{
-    if (url.startsWith(QLatin1String("qrc:"), Qt::CaseInsensitive)) {
-        if (url.length() > 4)
-            return QLatin1Char(':') + url.mid(4);
-        return QString();
-    }
-
-    return toLocalFile(url);
-}
-
 void QQmlEnginePrivate::sendQuit()
 {
     Q_Q(QQmlEngine);
index 21a03d6..8d250ed 100644 (file)
@@ -104,6 +104,8 @@ public:
     void setPluginPathList(const QStringList &paths);
     void addPluginPath(const QString& dir);
 
+    bool addNamedBundle(const QString &name, const QString &fileName);
+
     bool importPlugin(const QString &filePath, const QString &uri, QString *errorString); // XXX: Qt 5: Remove this function
     bool importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors);
 
@@ -136,7 +138,6 @@ public:
     enum ObjectOwnership { CppOwnership, JavaScriptOwnership };
     static void setObjectOwnership(QObject *, ObjectOwnership);
     static ObjectOwnership objectOwnership(QObject *);
-
 protected:
     virtual bool event(QEvent *);
 
index 2521888..4cfb8c7 100644 (file)
@@ -256,9 +256,6 @@ public:
     inline static QQmlEnginePrivate *get(QQmlContextData *c);
     inline static QQmlEngine *get(QQmlEnginePrivate *p);
 
-    static QString urlToLocalFileOrQrc(const QUrl& url);
-    static QString urlToLocalFileOrQrc(const QString& url);
-
     static void registerBaseTypes(const char *uri, int versionMajor, int versionMinor);
     static void defineModule();
 
diff --git a/src/qml/qml/qqmlfile.cpp b/src/qml/qml/qqmlfile.cpp
new file mode 100644 (file)
index 0000000..6d48536
--- /dev/null
@@ -0,0 +1,804 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlfile.h"
+
+#include <QtCore/qurl.h>
+#include <QtCore/qobject.h>
+#include <private/qqmlengine_p.h>
+#include <private/qqmlglobal_p.h>
+
+/*!
+\class The QQmlFile class gives access to local and remote files.
+
+Supports file://, qrc://, bundle:// uris and whatever QNetworkAccessManager supports.
+*/
+
+#define QQMLFILE_MAX_REDIRECT_RECURSION 16
+
+QT_BEGIN_NAMESPACE
+
+static QString qrc_string(QLatin1String("qrc"));
+static QString file_string(QLatin1String("file"));
+static QString bundle_string(QLatin1String("bundle"));
+
+class QQmlFilePrivate;
+class QQmlFileNetworkReply : public QObject
+{
+Q_OBJECT
+public:
+    QQmlFileNetworkReply(QQmlEngine *, QQmlFilePrivate *, const QUrl &);
+    ~QQmlFileNetworkReply();
+
+signals:
+    void finished();
+    void downloadProgress(qint64, qint64);
+
+public slots:
+    void networkFinished();
+    void networkDownloadProgress(qint64, qint64);
+
+public:
+    static int finishedIndex;
+    static int downloadProgressIndex;
+    static int networkFinishedIndex;
+    static int networkDownloadProgressIndex;
+    static int replyFinishedIndex;
+    static int replyDownloadProgressIndex;
+
+private:
+    QQmlEngine *m_engine;
+    QQmlFilePrivate *m_p;
+
+    int m_redirectCount;
+    QNetworkReply *m_reply;
+};
+
+class QQmlFilePrivate
+{
+public:
+    QQmlFilePrivate();
+
+    mutable QUrl url;
+    mutable QString urlString;
+
+    QQmlBundleData *bundle;
+    const QQmlBundle::FileEntry *file;
+
+    QByteArray data;
+
+    enum Error {
+        None, NotFound, CaseMismatch, Network
+    };
+
+    Error error;
+    QString errorString;
+
+    QQmlFileNetworkReply *reply;
+};
+
+int QQmlFileNetworkReply::finishedIndex = -1;
+int QQmlFileNetworkReply::downloadProgressIndex = -1;
+int QQmlFileNetworkReply::networkFinishedIndex = -1;
+int QQmlFileNetworkReply::networkDownloadProgressIndex = -1;
+int QQmlFileNetworkReply::replyFinishedIndex = -1;
+int QQmlFileNetworkReply::replyDownloadProgressIndex = -1;
+
+QQmlFileNetworkReply::QQmlFileNetworkReply(QQmlEngine *e, QQmlFilePrivate *p, const QUrl &url)
+: m_engine(e), m_p(p), m_redirectCount(0), m_reply(0)
+{
+    if (finishedIndex == -1) {
+        const QMetaObject *smo = &staticMetaObject;
+        finishedIndex = smo->indexOfSignal("finished()");
+        downloadProgressIndex = smo->indexOfSignal("downloadProgress(qint64,qint64)");
+        networkFinishedIndex = smo->indexOfMethod("networkFinished()");
+        networkDownloadProgressIndex = smo->indexOfMethod("networkDownloadProgress(qint64,qint64)");
+
+        const QMetaObject *rsmo = &QNetworkReply::staticMetaObject;
+        replyFinishedIndex = rsmo->indexOfSignal("finished()");
+        replyDownloadProgressIndex = rsmo->indexOfSignal("downloadProgress(qint64,qint64)");
+    }
+    Q_ASSERT(finishedIndex != -1 && downloadProgressIndex != -1 &&
+             networkFinishedIndex != -1 && networkDownloadProgressIndex != -1 &&
+             replyFinishedIndex != -1 && replyDownloadProgressIndex != -1);
+
+    QNetworkRequest req(url);
+    req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
+
+    m_reply = m_engine->networkAccessManager()->get(req);
+    QMetaObject::connect(m_reply, replyFinishedIndex, this, networkFinishedIndex);
+    QMetaObject::connect(m_reply, replyDownloadProgressIndex, this, networkDownloadProgressIndex);
+}
+
+QQmlFileNetworkReply::~QQmlFileNetworkReply()
+{
+    if (m_reply) {
+        m_reply->disconnect();
+        m_reply->deleteLater();
+    }
+}
+
+void QQmlFileNetworkReply::networkFinished()
+{
+    ++m_redirectCount;
+    if (m_redirectCount < QQMLFILE_MAX_REDIRECT_RECURSION) {
+        QVariant redirect = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
+        if (redirect.isValid()) {
+            QUrl url = m_reply->url().resolved(redirect.toUrl());
+
+            QNetworkRequest req(url);
+            req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
+
+            m_reply->deleteLater();
+            m_reply = m_engine->networkAccessManager()->get(req);
+
+            QMetaObject::connect(m_reply, replyFinishedIndex,
+                                 this, networkFinishedIndex);
+            QMetaObject::connect(m_reply, replyDownloadProgressIndex,
+                                 this, networkDownloadProgressIndex);
+
+            return;
+        }
+    }
+
+    if (m_reply->error()) {
+        m_p->errorString = m_reply->errorString();
+        m_p->error = QQmlFilePrivate::Network;
+    } else {
+        m_p->data = m_reply->readAll();
+    }
+
+    m_reply->deleteLater();
+    m_reply = 0;
+
+    m_p->reply = 0;
+    emit finished();
+    delete this;
+}
+
+void QQmlFileNetworkReply::networkDownloadProgress(qint64 a, qint64 b)
+{
+    emit downloadProgress(a, b);
+}
+
+QQmlFilePrivate::QQmlFilePrivate()
+: bundle(0), file(0), error(None), reply(0)
+{
+}
+
+QQmlFile::QQmlFile()
+: d(new QQmlFilePrivate)
+{
+}
+
+QQmlFile::QQmlFile(QQmlEngine *e, const QUrl &url)
+: d(new QQmlFilePrivate)
+{
+    load(e, url);
+}
+
+QQmlFile::QQmlFile(QQmlEngine *e, const QString &url)
+: d(new QQmlFilePrivate)
+{
+    load(e, url);
+}
+
+QQmlFile::~QQmlFile()
+{
+    if (d->reply)
+        delete d->reply;
+    if (d->bundle)
+        d->bundle->release();
+
+    delete d;
+    d = 0;
+}
+
+bool QQmlFile::isNull() const
+{
+    return status() == Null;
+}
+
+bool QQmlFile::isReady() const
+{
+    return status() == Ready;
+}
+
+bool QQmlFile::isError() const
+{
+    return status() == Error;
+}
+
+bool QQmlFile::isLoading() const
+{
+    return status() == Loading;
+}
+
+QUrl QQmlFile::url() const
+{
+    if (!d->urlString.isEmpty()) {
+        d->url = QUrl(d->urlString);
+        d->urlString = QString();
+    }
+    return d->url;
+}
+
+QQmlFile::Status QQmlFile::status() const
+{
+    if (d->url.isEmpty() && d->urlString.isEmpty())
+        return Null;
+    else if (d->reply)
+        return Loading;
+    else if (d->error != QQmlFilePrivate::None)
+        return Error;
+    else
+        return Ready;
+}
+
+QString QQmlFile::error() const
+{
+    switch (d->error) {
+    default:
+    case QQmlFilePrivate::None:
+        return QString();
+    case QQmlFilePrivate::NotFound:
+        return QLatin1String("File not found");
+    case QQmlFilePrivate::CaseMismatch:
+        return QLatin1String("File name case mismatch");
+    }
+}
+
+qint64 QQmlFile::size() const
+{
+    if (d->file) return d->file->fileSize();
+    else return d->data.size();
+}
+
+const char *QQmlFile::data() const
+{
+    if (d->file) return d->file->contents();
+    else return d->data.constData();
+}
+
+QByteArray QQmlFile::dataByteArray() const
+{
+    if (d->file) return QByteArray(d->file->contents(), d->file->fileSize());
+    else return d->data;
+}
+
+QByteArray QQmlFile::metaData(const QString &name) const
+{
+    if (d->file) {
+        Q_ASSERT(d->bundle);
+        const QQmlBundle::FileEntry *meta = d->bundle->link(d->file, name);
+        if (meta)
+            return QByteArray::fromRawData(meta->contents(), meta->fileSize());
+    }
+    return QByteArray();
+}
+
+void QQmlFile::load(QQmlEngine *engine, const QUrl &url)
+{
+    Q_ASSERT(engine);
+
+    QString scheme = url.scheme();
+
+    clear();
+    d->url = url;
+
+    if (isBundle(url)) {
+        // Bundle
+        QQmlEnginePrivate *p = QQmlEnginePrivate::get(engine);
+        QQmlBundleData *bundle = p->typeLoader.getBundle(url.host());
+
+        d->error = QQmlFilePrivate::NotFound;
+
+        if (bundle) {
+            QString filename = url.path().mid(1);
+            const QQmlBundle::FileEntry *entry = bundle->find(filename);
+            if (entry) {
+                d->file = entry;
+                d->bundle = bundle;
+                d->bundle->addref();
+                d->error = QQmlFilePrivate::None;
+            }
+            bundle->release();
+        }
+    } else if (isLocalFile(url)) {
+        QString lf = urlToLocalFileOrQrc(url);
+
+        if (!QQml_isFileCaseCorrect(lf)) {
+            d->error = QQmlFilePrivate::CaseMismatch;
+            return;
+        }
+
+        QFile file(lf);
+        if (file.open(QFile::ReadOnly)) {
+            d->data = file.readAll();
+        } else {
+            d->error = QQmlFilePrivate::NotFound;
+        }
+    } else {
+        d->reply = new QQmlFileNetworkReply(engine, d, url);
+    }
+}
+
+void QQmlFile::load(QQmlEngine *engine, const QString &url)
+{
+    Q_ASSERT(engine);
+
+    clear();
+
+    d->urlString = url;
+
+    if (isBundle(url)) {
+        // Bundle
+        QQmlEnginePrivate *p = QQmlEnginePrivate::get(engine);
+
+        d->error = QQmlFilePrivate::NotFound;
+
+        int index = url.indexOf(QLatin1Char('/'), 9);
+        if (index == -1)
+            return;
+
+        QStringRef identifier(&url, 9, index - 9);
+
+        QQmlBundleData *bundle = p->typeLoader.getBundle(identifier);
+
+        d->error = QQmlFilePrivate::NotFound;
+
+        if (bundle) {
+            QString filename = url.mid(index);
+            const QQmlBundle::FileEntry *entry = bundle->find(filename);
+            if (entry) {
+                d->data = QByteArray(entry->contents(), entry->fileSize());
+                d->error = QQmlFilePrivate::None;
+            }
+            bundle->release();
+        }
+
+    } else if (isLocalFile(url)) {
+        QString lf = urlToLocalFileOrQrc(url);
+
+        if (!QQml_isFileCaseCorrect(lf)) {
+            d->error = QQmlFilePrivate::CaseMismatch;
+            return;
+        }
+
+        QFile file(lf);
+        if (file.open(QFile::ReadOnly)) {
+            d->data = file.readAll();
+        } else {
+            d->error = QQmlFilePrivate::NotFound;
+        }
+    } else {
+        QUrl qurl(url);
+        d->url = qurl;
+        d->urlString = QString();
+        d->reply = new QQmlFileNetworkReply(engine, d, qurl);
+    }
+}
+
+void QQmlFile::clear()
+{
+    d->url = QUrl();
+    d->urlString = QString();
+    d->data = QByteArray();
+    if (d->bundle) d->bundle->release();
+    d->bundle = 0;
+    d->file = 0;
+    d->error = QQmlFilePrivate::None;
+}
+
+void QQmlFile::clear(QObject *)
+{
+    clear();
+}
+
+bool QQmlFile::connectFinished(QObject *object, const char *method)
+{
+    if (!d || !d->reply) {
+        qWarning("QQmlFile: connectFinished() called when not loading.");
+        return false;
+    }
+
+    return QObject::connect(d->reply, SIGNAL(finished()),
+                            object, method);
+}
+
+bool QQmlFile::connectFinished(QObject *object, int method)
+{
+    if (!d || !d->reply) {
+        qWarning("QQmlFile: connectFinished() called when not loading.");
+        return false;
+    }
+
+    return QMetaObject::connect(d->reply, QQmlFileNetworkReply::finishedIndex,
+                                object, method);
+}
+
+bool QQmlFile::connectDownloadProgress(QObject *object, const char *method)
+{
+    if (!d || !d->reply) {
+        qWarning("QQmlFile: connectDownloadProgress() called when not loading.");
+        return false;
+    }
+
+    return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)),
+                            object, method);
+}
+
+bool QQmlFile::connectDownloadProgress(QObject *object, int method)
+{
+    if (!d || !d->reply) {
+        qWarning("QQmlFile: connectDownloadProgress() called when not loading.");
+        return false;
+    }
+
+    return QMetaObject::connect(d->reply, QQmlFileNetworkReply::downloadProgressIndex,
+                                object, method);
+}
+
+/*!
+Returns true if QQmlFile will open \a url synchronously.
+
+Synchronous urls have a qrc://, file://, or bundle:// scheme.
+*/
+bool QQmlFile::isSynchronous(const QUrl &url)
+{
+    QString scheme = url.scheme();
+
+    if ((scheme.length() == 4 && 0 == scheme.compare(file_string, Qt::CaseInsensitive)) ||
+        (scheme.length() == 6 && 0 == scheme.compare(bundle_string, Qt::CaseInsensitive)) ||
+        (scheme.length() == 3 && 0 == scheme.compare(qrc_string, Qt::CaseInsensitive)))
+        return true;
+    else
+        return false;
+}
+
+/*!
+Returns true if QQmlFile will open \a url synchronously.
+
+Synchronous urls have a qrc://, file://, or bundle:// scheme.
+*/
+bool QQmlFile::isSynchronous(const QString &url)
+{
+    if (url.length() < 6 /* qrc:// */)
+        return false;
+
+    QChar f = url[0];
+
+    if (f == QLatin1Char('f') || f == QLatin1Char('F')) {
+
+        return url.length() >= 7 /* file:// */ &&
+               url.startsWith(file_string, Qt::CaseInsensitive) &&
+               url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/');
+
+    } else if (f == QLatin1Char('b') || f == QLatin1Char('B')) {
+
+        return url.length() >= 9 /* bundle:// */ &&
+               url.startsWith(bundle_string, Qt::CaseInsensitive) &&
+               url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/') && url[8] == QLatin1Char('/');
+
+    } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) {
+
+        return url.length() >= 6 /* bundle:// */ &&
+               url.startsWith(qrc_string, Qt::CaseInsensitive) &&
+               url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/') && url[5] == QLatin1Char('/');
+
+    }
+
+    return false;
+}
+
+/*!
+Returns true if \a url is a bundle.
+
+Bundle urls have a bundle:// scheme.
+*/
+bool QQmlFile::isBundle(const QString &url)
+{
+    return url.length() >= 9 && url.startsWith(bundle_string, Qt::CaseInsensitive) &&
+           url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/') && url[8] == QLatin1Char('/');
+}
+
+/*!
+Returns true if \a url is a bundle.
+
+Bundle urls have a bundle:// scheme.
+*/
+bool QQmlFile::isBundle(const QUrl &url)
+{
+    QString scheme = url.scheme();
+
+    return scheme.length() == 6 && 0 == scheme.compare(bundle_string, Qt::CaseInsensitive);
+}
+
+/*!
+Returns true if \a url is a local file that can be opened with QFile.
+
+Local file urls have either a qrc:// or file:// scheme.
+*/
+bool QQmlFile::isLocalFile(const QUrl &url)
+{
+    QString scheme = url.scheme();
+
+    if ((scheme.length() == 4 && 0 == scheme.compare(file_string, Qt::CaseInsensitive)) ||
+        (scheme.length() == 3 && 0 == scheme.compare(qrc_string, Qt::CaseInsensitive)))
+        return true;
+    else
+        return false;
+}
+
+/*!
+Returns true if \a url is a local file that can be opened with QFile.
+
+Local file urls have either a qrc:// or file:// scheme.
+*/
+bool QQmlFile::isLocalFile(const QString &url)
+{
+    if (url.length() < 6 /* qrc:// */)
+        return false;
+
+    QChar f = url[0];
+
+    if (f == QLatin1Char('f') || f == QLatin1Char('F')) {
+
+        return url.length() >= 7 /* file:// */ &&
+               url.startsWith(file_string, Qt::CaseInsensitive) &&
+               url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/');
+
+    } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) {
+
+        return url.length() >= 6 /* bundle:// */ &&
+               url.startsWith(qrc_string, Qt::CaseInsensitive) &&
+               url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/') && url[5] == QLatin1Char('/');
+
+    }
+
+    return false;
+}
+
+/*!
+If \a url is a local file returns a path suitable for passing to QFile.  Otherwise returns an
+empty string.
+*/
+QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url)
+{
+    if (url.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) == 0) {
+        if (url.authority().isEmpty())
+            return QLatin1Char(':') + url.path();
+        return QString();
+    }
+    return url.toLocalFile();
+}
+
+static QString toLocalFile(const QString &url)
+{
+    if (!url.startsWith(QLatin1String("file://"), Qt::CaseInsensitive))
+        return QString();
+
+    QString file = url.mid(7);
+
+    //XXX TODO: handle windows hostnames: "//servername/path/to/file.txt"
+
+    // magic for drives on windows
+    if (file.length() > 2 && file.at(0) == QLatin1Char('/') && file.at(2) == QLatin1Char(':'))
+        file.remove(0, 1);
+
+    return file;
+}
+
+/*!
+If \a url is a local file returns a path suitable for passing to QFile.  Otherwise returns an
+empty string.
+*/
+QString QQmlFile::urlToLocalFileOrQrc(const QString& url)
+{
+    if (url.startsWith(QLatin1String("qrc:"), Qt::CaseInsensitive)) {
+        if (url.length() > 4)
+            return QLatin1Char(':') + url.mid(4);
+        return QString();
+    }
+
+    return toLocalFile(url);
+}
+
+bool QQmlFile::bundleDirectoryExists(const QString &dir, QQmlEngine *e)
+{
+    if (!isBundle(dir))
+        return false;
+
+    int index = dir.indexOf(QLatin1Char('/'), 9);
+
+    if (index == -1 && dir.length() > 9) // We accept "bundle://<blah>" with no extra path
+        index = dir.length();
+
+    if (index == -1)
+        return false;
+
+    QStringRef identifier(&dir, 9, index - 9);
+    QStringRef path(&dir, index + 1, dir.length() - index - 1);
+
+    QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
+
+    if (bundle) {
+        int lastIndex = dir.lastIndexOf(QLatin1Char('/'));
+
+        if (lastIndex <= index) {
+            bundle->release();
+            return true;
+        }
+
+        QStringRef d(&dir, index + 1, lastIndex - index);
+
+        QList<const QQmlBundle::FileEntry *> entries = bundle->files();
+
+        for (int ii = 0; ii < entries.count(); ++ii) {
+            QString name = entries.at(ii)->fileName();
+            if (name.startsWith(d)) {
+                bundle->release();
+                return true;
+            }
+        }
+
+        bundle->release();
+    }
+
+    return false;
+}
+
+bool QQmlFile::bundleDirectoryExists(const QUrl &url, QQmlEngine *e)
+{
+    if (!isBundle(url))
+        return false;
+
+    QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(url.host());
+
+    if (bundle) {
+        QString path = url.path();
+
+        int lastIndex = path.lastIndexOf(QLatin1Char('/'));
+
+        if (lastIndex == -1) {
+            bundle->release();
+            return true;
+        }
+
+        QStringRef d(&path, 0, lastIndex);
+
+        QList<const QQmlBundle::FileEntry *> entries = bundle->files();
+
+        for (int ii = 0; ii < entries.count(); ++ii) {
+            QString name = entries.at(ii)->fileName();
+            if (name.startsWith(d)) {
+                bundle->release();
+                return true;
+            }
+        }
+
+        bundle->release();
+    }
+
+    return false;
+}
+
+bool QQmlFile::bundleFileExists(const QString &file, QQmlEngine *e)
+{
+    if (!isBundle(file))
+        return false;
+
+    int index = file.indexOf(QLatin1Char('/'), 9);
+
+    if (index == -1)
+        return false;
+
+    QStringRef identifier(&file, 9, index - 9);
+    QStringRef path(&file, index + 1, file.length() - index - 1);
+
+    QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
+
+    if (bundle) {
+        const QQmlBundle::FileEntry *entry = bundle->find(path.constData(), path.length());
+        bundle->release();
+
+        return entry != 0;
+    }
+
+    return false;
+}
+
+bool QQmlFile::bundleFileExists(const QUrl &, QQmlEngine *)
+{
+    qFatal("Not implemented");
+    return true;
+}
+
+/*!
+Returns the file name for the bundle file referenced by \a url or an
+empty string if \a url isn't a bundle url.
+*/
+QString QQmlFile::bundleFileName(const QString &url, QQmlEngine *e)
+{
+    if (!isBundle(url))
+        return QString();
+
+    int index = url.indexOf(QLatin1Char('/'), 9);
+
+    if (index == -1)
+        index = url.length();
+
+    QStringRef identifier(&url, 9, index - 9);
+
+    QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
+
+    if (bundle) {
+        QString rv = bundle->fileName;
+        bundle->release();
+        return rv;
+    }
+
+    return QString();
+}
+
+/*!
+Returns the file name for the bundle file referenced by \a url or an
+empty string if \a url isn't a bundle url.
+*/
+QString QQmlFile::bundleFileName(const QUrl &url, QQmlEngine *e)
+{
+    if (!isBundle(url))
+        return QString();
+
+    QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(url.host());
+
+    if (bundle) {
+        QString rv = bundle->fileName;
+        bundle->release();
+        return rv;
+    }
+
+    return QString();
+}
+
+QT_END_NAMESPACE
+
+#include "qqmlfile.moc"
diff --git a/src/qml/qml/qqmlfile.h b/src/qml/qml/qqmlfile.h
new file mode 100644 (file)
index 0000000..cfa833c
--- /dev/null
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLFILE_H
+#define QQMLFILE_H
+
+#include <QtQml/qtqmlglobal.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QUrl;
+class QString;
+class QObject;
+class QQmlEngine;
+class QQmlFilePrivate;
+
+class Q_QML_EXPORT QQmlFile
+{
+public:
+    QQmlFile();
+    QQmlFile(QQmlEngine *, const QUrl &);
+    QQmlFile(QQmlEngine *, const QString &);
+    ~QQmlFile();
+
+    enum Status { Null, Ready, Error, Loading };
+
+    bool isNull() const;
+    bool isReady() const;
+    bool isError() const;
+    bool isLoading() const;
+
+    QUrl url() const;
+
+    Status status() const;
+    QString error() const;
+
+    qint64 size() const;
+    const char *data() const;
+    QByteArray dataByteArray() const;
+
+    QByteArray metaData(const QString &) const;
+
+    void load(QQmlEngine *, const QUrl &);
+    void load(QQmlEngine *, const QString &);
+
+    void clear();
+    void clear(QObject *);
+
+    bool connectFinished(QObject *, const char *);
+    bool connectFinished(QObject *, int);
+    bool connectDownloadProgress(QObject *, const char *);
+    bool connectDownloadProgress(QObject *, int);
+
+    static bool isSynchronous(const QString &url);
+    static bool isSynchronous(const QUrl &url);
+
+    static bool isBundle(const QString &url);
+    static bool isBundle(const QUrl &url);
+
+    static bool isLocalFile(const QString &url);
+    static bool isLocalFile(const QUrl &url);
+
+    static QString urlToLocalFileOrQrc(const QString &);
+    static QString urlToLocalFileOrQrc(const QUrl &);
+
+    static bool bundleDirectoryExists(const QString &, QQmlEngine *);
+    static bool bundleDirectoryExists(const QUrl &, QQmlEngine *);
+
+    static bool bundleFileExists(const QString &, QQmlEngine *);
+    static bool bundleFileExists(const QUrl &, QQmlEngine *);
+
+    static QString bundleFileName(const QString &, QQmlEngine *);
+    static QString bundleFileName(const QUrl &, QQmlEngine *);
+
+private:
+    Q_DISABLE_COPY(QQmlFile)
+    QQmlFilePrivate *d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QQMLFILE_H
index a8a3ed0..1417e3d 100644 (file)
@@ -43,6 +43,7 @@
 
 #include <QtCore/qdebug.h>
 #include <QtCore/qdir.h>
+#include <QtQml/qqmlfile.h>
 #include <QtCore/qfileinfo.h>
 #include <QtCore/qpluginloader.h>
 #include <QtCore/qlibraryinfo.h>
@@ -56,11 +57,6 @@ QT_BEGIN_NAMESPACE
 DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
 DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
 
-static bool greaterThan(const QString &s1, const QString &s2)
-{
-    return s1 > s2;
-}
-
 QString resolveLocalUrl(const QString &url, const QString &relative)
 {
     if (relative.contains(QLatin1Char(':'))) {
@@ -109,15 +105,13 @@ QString resolveLocalUrl(const QString &url, const QString &relative)
     }
 }
 
-
-
 typedef QMap<QString, QString> StringStringMap;
 Q_GLOBAL_STATIC(StringStringMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri
 
-class QQmlImportedNamespace 
+class QQmlImportNamespace
 {
 public:
-    struct Data {
+    struct Import {
         QString uri;
         QString url;
         int majversion;
@@ -125,45 +119,58 @@ public:
         bool isLibrary;
         QQmlDirComponents qmlDirComponents;
         QQmlDirScripts qmlDirScripts;
-    };
-    QList<Data> imports;
 
+        bool resolveType(QQmlTypeLoader *typeLoader, const QString& type,
+                         int *vmajor, int *vminor,
+                         QQmlType** type_return, QString* url_return,
+                         QString *base = 0, bool *typeRecursionDetected = 0) const;
+    };
+    QList<Import> imports;
 
-    bool find_helper(QQmlTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor,
-                                 QQmlType** type_return, QString* url_return,
-                                 QString *base = 0, bool *typeRecursionDetected = 0);
-    bool find(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
-              QString* url_return, QString *base = 0, QList<QQmlError> *errors = 0);
+    bool resolveType(QQmlTypeLoader *typeLoader, const QString& type,
+                     int *vmajor, int *vminor,
+                     QQmlType** type_return, QString* url_return,
+                     QString *base = 0, QList<QQmlError> *errors = 0);
 };
 
-class QQmlImportsPrivate {
+class QQmlImportsPrivate
+{
 public:
     QQmlImportsPrivate(QQmlTypeLoader *loader);
     ~QQmlImportsPrivate();
 
-    bool importExtension(const QString &absoluteFilePath, const QString &uri, 
-                         QQmlImportDatabase *database, QQmlDirComponents* components, 
-                         QQmlDirScripts *scripts,
-                         QList<QQmlError> *errors);
-
-    QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database);
-    QString add(const QQmlDirComponents &qmldircomponentsnetwork,
-             const QString& uri_arg, const QString& prefix, 
-             int vmaj, int vmin, QQmlScript::Import::Type importType, 
-             QQmlImportDatabase *database, QList<QQmlError> *errors);
-    bool find(const QString& type, int *vmajor, int *vminor,
-              QQmlType** type_return, QString* url_return, QList<QQmlError> *errors);
+    bool addImport(const QQmlDirComponents &qmldircomponentsnetwork,
+                   const QString &importedUri, const QString& prefix,
+                   int vmaj, int vmin, QQmlScript::Import::Type importType,
+                   bool isImplicitImport, QQmlImportDatabase *database,
+                   QString *, QList<QQmlError> *errors);
 
-    QQmlImportedNamespace *findNamespace(const QString& type);
+    bool resolveType(const QString& type, int *vmajor, int *vminor,
+                     QQmlType** type_return, QString* url_return,
+                     QList<QQmlError> *errors);
 
     QUrl baseUrl;
     QString base;
     int ref;
 
     QSet<QString> qmlDirFilesForWhichPluginsHaveBeenLoaded;
-    QQmlImportedNamespace unqualifiedset;
-    QHash<QString,QQmlImportedNamespace* > set;
+    QQmlImportNamespace unqualifiedset;
+    QHash<QString, QQmlImportNamespace *> set;
     QQmlTypeLoader *typeLoader;
+
+    static inline QString tr(const char *str) {
+        return QQmlImportDatabase::tr(str);
+    }
+
+private:
+    static bool locateQmldir(const QString &uri, int vmaj, int vmin,
+                             QQmlImportDatabase *database,
+                             QString *outQmldirFilePath, QString *outUrl);
+    bool importExtension(const QString &absoluteFilePath, const QString &uri,
+                         QQmlImportDatabase *database, QQmlDirComponents* components,
+                         QQmlDirScripts *scripts,
+                         QString *url, QList<QQmlError> *errors);
+    QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database);
 };
 
 /*!
@@ -223,29 +230,30 @@ QUrl QQmlImports::baseUrl() const
 
 void QQmlImports::populateCache(QQmlTypeNameCache *cache, QQmlEngine *engine) const
 {
-    const QQmlImportedNamespace &set = d->unqualifiedset;
+    const QQmlImportNamespace &set = d->unqualifiedset;
 
     for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
-        const QQmlImportedNamespace::Data &data = set.imports.at(ii);
-        QQmlTypeModule *module = QQmlMetaType::typeModule(data.uri, data.majversion);
+        const QQmlImportNamespace::Import &import = set.imports.at(ii);
+        QQmlTypeModule *module = QQmlMetaType::typeModule(import.uri, import.majversion);
         if (module)
-            cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, data.minversion));
+            cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import.minversion));
     }
 
-    for (QHash<QString,QQmlImportedNamespace* >::ConstIterator iter = d->set.begin();
+    for (QHash<QString, QQmlImportNamespace* >::ConstIterator iter = d->set.begin();
          iter != d->set.end(); 
          ++iter) {
 
-        const QQmlImportedNamespace &set = *iter.value();
+        const QQmlImportNamespace &set = *iter.value();
         for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
-            const QQmlImportedNamespace::Data &data = set.imports.at(ii);
-            QQmlTypeModule *module = QQmlMetaType::typeModule(data.uri, data.majversion);
+            const QQmlImportNamespace::Import &import = set.imports.at(ii);
+            QQmlTypeModule *module = QQmlMetaType::typeModule(import.uri, import.majversion);
             if (module) {
-                QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()];
-                import.modules.append(QQmlTypeModuleVersion(module, data.minversion));
+                QQmlTypeNameCache::Import &typeimport = cache->m_namedImports[iter.key()];
+                typeimport.modules.append(QQmlTypeModuleVersion(module, import.minversion));
             }
 
-            QQmlMetaType::ModuleApi moduleApi = QQmlMetaType::moduleApi(data.uri, data.majversion, data.minversion);
+            QQmlMetaType::ModuleApi moduleApi = QQmlMetaType::moduleApi(import.uri, import.majversion,
+                                                                        import.minversion);
             if (moduleApi.script || moduleApi.qobject) {
                 QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()];
                 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
@@ -259,32 +267,32 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
 {
     QList<QQmlImports::ScriptReference> scripts;
 
-    const QQmlImportedNamespace &set = d->unqualifiedset;
+    const QQmlImportNamespace &set = d->unqualifiedset;
 
     for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
-        const QQmlImportedNamespace::Data &data = set.imports.at(ii);
+        const QQmlImportNamespace::Import &import = set.imports.at(ii);
 
-        foreach (const QQmlDirParser::Script &script, data.qmlDirScripts) {
+        foreach (const QQmlDirParser::Script &script, import.qmlDirScripts) {
             ScriptReference ref;
             ref.nameSpace = script.nameSpace;
-            ref.location = QUrl(data.url).resolved(QUrl(script.fileName));
+            ref.location = QUrl(import.url).resolved(QUrl(script.fileName));
             scripts.append(ref);
         }
     }
 
-    for (QHash<QString,QQmlImportedNamespace* >::ConstIterator iter = d->set.constBegin();
+    for (QHash<QString, QQmlImportNamespace* >::ConstIterator iter = d->set.constBegin();
          iter != d->set.constEnd();
          ++iter) {
-        const QQmlImportedNamespace &set = *iter.value();
+        const QQmlImportNamespace &set = *iter.value();
 
         for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
-            const QQmlImportedNamespace::Data &data = set.imports.at(ii);
+            const QQmlImportNamespace::Import &import = set.imports.at(ii);
 
-            foreach (const QQmlDirParser::Script &script, data.qmlDirScripts) {
+            foreach (const QQmlDirParser::Script &script, import.qmlDirScripts) {
                 ScriptReference ref;
                 ref.nameSpace = script.nameSpace;
                 ref.qualifier = iter.key();
-                ref.location = QUrl(data.url).resolved(QUrl(script.fileName));
+                ref.location = QUrl(import.url).resolved(QUrl(script.fileName));
                 scripts.append(ref);
             }
         }
@@ -298,7 +306,7 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
 
   The given (namespace qualified) \a type is resolved to either
   \list
-  \li a QQmlImportedNamespace stored at \a ns_return,
+  \li a QQmlImportNamespace stored at \a ns_return,
   \li a QQmlType stored at \a type_return, or
   \li a component located at \a url_return.
   \endlist
@@ -308,27 +316,29 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
   \sa addImport()
 */
 bool QQmlImports::resolveType(const QString& type,
-                                      QQmlType** type_return, QString* url_return, int *vmaj, int *vmin,
-                                      QQmlImportedNamespace** ns_return, QList<QQmlError> *errors) const
+                              QQmlType** type_return, QString* url_return, int *vmaj, int *vmin,
+                              QQmlImportNamespace** ns_return, QList<QQmlError> *errors) const
 {
-    QQmlImportedNamespace* ns = d->findNamespace(type);
+    QQmlImportNamespace* ns = d->set.value(type);
     if (ns) {
         if (ns_return)
             *ns_return = ns;
         return true;
     }
     if (type_return || url_return) {
-        if (d->find(type,vmaj,vmin,type_return,url_return, errors)) {
+        if (d->resolveType(type,vmaj,vmin,type_return,url_return, errors)) {
             if (qmlImportTrace()) {
+#define RESOLVE_TYPE_DEBUG qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) \
+                                              << ")" << "::resolveType: " << type << " => "
+
                 if (type_return && *type_return && url_return && !url_return->isEmpty())
-                    qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " 
-                                       << type << " => " << (*type_return)->typeName() << " " << *url_return;
+                    RESOLVE_TYPE_DEBUG << (*type_return)->typeName() << " " << *url_return;
                 if (type_return && *type_return)
-                    qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " 
-                                       << type << " => " << (*type_return)->typeName();
+                    RESOLVE_TYPE_DEBUG << (*type_return)->typeName();
                 if (url_return && !url_return->isEmpty())
-                    qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " 
-                                       << type << " => " << *url_return;
+                    RESOLVE_TYPE_DEBUG << *url_return;
+
+#undef RESOLVE_TYPE_DEBUG
             }
             return true;
         }
@@ -346,42 +356,40 @@ bool QQmlImports::resolveType(const QString& type,
 
   If either return pointer is 0, the corresponding search is not done.
 */
-bool QQmlImports::resolveType(QQmlImportedNamespace* ns, const QString& type,
-                                      QQmlType** type_return, QString* url_return,
-                                      int *vmaj, int *vmin) const
+bool QQmlImports::resolveType(QQmlImportNamespace* ns, const QString& type,
+                              QQmlType** type_return, QString* url_return,
+                              int *vmaj, int *vmin) const
 {
-    return ns->find(d->typeLoader,type,vmaj,vmin,type_return,url_return);
+    return ns->resolveType(d->typeLoader,type,vmaj,vmin,type_return,url_return);
 }
 
-bool QQmlImportedNamespace::find_helper(QQmlTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor,
-                                 QQmlType** type_return, QString* url_return,
-                                 QString *base, bool *typeRecursionDetected)
+bool QQmlImportNamespace::Import::resolveType(QQmlTypeLoader *typeLoader,
+                                              const QString& type, int *vmajor, int *vminor,
+                                              QQmlType** type_return, QString* url_return,
+                                              QString *base, bool *typeRecursionDetected) const
 {
-    int vmaj = data.majversion;
-    int vmin = data.minversion;
-
-    if (vmaj >= 0 && vmin >= 0) {
-        QString qt = data.uri + QLatin1Char('/') + type;
-        QQmlType *t = QQmlMetaType::qmlType(qt,vmaj,vmin);
+    if (majversion >= 0 && minversion >= 0) {
+        QString qt = uri + QLatin1Char('/') + type;
+        QQmlType *t = QQmlMetaType::qmlType(qt, majversion, minversion);
         if (t) {
-            if (vmajor) *vmajor = vmaj;
-            if (vminor) *vminor = vmin;
+            if (vmajor) *vmajor = majversion;
+            if (vminor) *vminor = minversion;
             if (type_return)
                 *type_return = t;
             return true;
         }
     }
 
-    const QQmlDirComponents &qmldircomponents = data.qmlDirComponents;
     bool typeWasDeclaredInQmldir = false;
-    if (!qmldircomponents.isEmpty()) {
-        foreach (const QQmlDirParser::Component &c, qmldircomponents) {
+    if (!qmlDirComponents.isEmpty()) {
+        foreach (const QQmlDirParser::Component &c, qmlDirComponents) {
             if (c.typeName == type) {
                 typeWasDeclaredInQmldir = true;
                 // importing version -1 means import ALL versions
-                if ((vmaj == -1) || (c.majorVersion == vmaj && vmin >= c.minorVersion)) {
-                    QString url(data.url + type + QLatin1String(".qml"));
-                    QString candidate = resolveLocalUrl(url, c.fileName);
+                if ((majversion == -1) || (c.majorVersion == majversion &&
+                                           minversion >= c.minorVersion)) {
+                    QString candidate = resolveLocalUrl(QString(url + type + QLatin1String(".qml")),
+                                                        c.fileName);
                     if (c.internal && base) {
                         if (resolveLocalUrl(*base, c.fileName) != candidate)
                             continue; // failed attempt to access an internal type
@@ -399,65 +407,205 @@ bool QQmlImportedNamespace::find_helper(QQmlTypeLoader *typeLoader, const Data &
         }
     }
 
-    if (!typeWasDeclaredInQmldir && !data.isLibrary) {
-        // XXX search non-files too! (eg. zip files, see QT-524)
-        QString url(data.url + type + QLatin1String(".qml"));
-        QString file = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
-        if (!typeLoader->absoluteFilePath(file).isEmpty()) {
-            if (base && *base == url) { // no recursion
+    if (!typeWasDeclaredInQmldir && !isLibrary) {
+        QString qmlUrl(url + type + QLatin1String(".qml"));
+
+        bool exists = false;
+
+        if (QQmlFile::isBundle(qmlUrl)) {
+            exists = QQmlFile::bundleFileExists(qmlUrl, typeLoader->engine());
+        } else {
+            QString file = QQmlFile::urlToLocalFileOrQrc(qmlUrl);
+            exists = !typeLoader->absoluteFilePath(QQmlFile::urlToLocalFileOrQrc(qmlUrl)).isEmpty();
+        }
+
+        if (exists) {
+            if (base && *base == qmlUrl) { // no recursion
                 if (typeRecursionDetected)
                     *typeRecursionDetected = true;
             } else {
                 if (url_return)
-                    *url_return = url;
+                    *url_return = qmlUrl;
                 return true;
             }
         }
     }
+
     return false;
 }
 
-QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
-    : ref(1), typeLoader(loader)
+bool QQmlImportsPrivate::resolveType(const QString& type, int *vmajor, int *vminor,
+                                     QQmlType** type_return, QString* url_return,
+                                     QList<QQmlError> *errors)
+{
+    QQmlImportNamespace *s = 0;
+    int slash = type.indexOf(QLatin1Char('/'));
+    if (slash >= 0) {
+        QString namespaceName = type.left(slash);
+        s = set.value(namespaceName);
+        if (!s) {
+            if (errors) {
+                QQmlError error;
+                error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName));
+                errors->prepend(error);
+            }
+            return false;
+        }
+        int nslash = type.indexOf(QLatin1Char('/'),slash+1);
+        if (nslash > 0) {
+            if (errors) {
+                QQmlError error;
+                error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
+                errors->prepend(error);
+            }
+            return false;
+        }
+    } else {
+        s = &unqualifiedset;
+    }
+    QString unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower)
+    if (s) {
+        if (s->resolveType(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors))
+            return true;
+        if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) {
+            // qualified, and only 1 url
+            *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype + QLatin1String(".qml"));
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor,
+                                      int *vminor, QQmlType** type_return,
+                                      QString* url_return, QString *base, QList<QQmlError> *errors)
 {
+    bool typeRecursionDetected = false;
+    for (int i=0; i<imports.count(); ++i) {
+        const Import &import = imports.at(i);
+        if (import.resolveType(typeLoader, type, vmajor, vminor, type_return, url_return,
+                               base, &typeRecursionDetected)) {
+            if (qmlCheckTypes()) {
+                // check for type clashes
+                for (int j = i+1; j<imports.count(); ++j) {
+                    const Import &import2 = imports.at(j);
+                    if (import2.resolveType(typeLoader, type, vmajor, vminor, 0, 0, base)) {
+                        if (errors) {
+                            QString u1 = imports.at(i).url;
+                            QString u2 = imports.at(j).url;
+                            if (base) {
+                                QString b = *base;
+                                int slash = b.lastIndexOf(QLatin1Char('/'));
+                                if (slash >= 0) {
+                                    b = b.left(slash+1);
+                                    QString l = b.left(slash);
+                                    if (u1.startsWith(b))
+                                        u1 = u1.mid(b.count());
+                                    else if (u1 == l)
+                                        u1 = QQmlImportDatabase::tr("local directory");
+                                    if (u2.startsWith(b))
+                                        u2 = u2.mid(b.count());
+                                    else if (u2 == l)
+                                        u2 = QQmlImportDatabase::tr("local directory");
+                                }
+                            }
+
+                            QQmlError error;
+                            if (u1 != u2) {
+                                error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
+                            } else {
+                                error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
+                                                        .arg(u1)
+                                                        .arg(imports.at(i).majversion).arg(imports.at(i).minversion)
+                                                        .arg(imports.at(j).majversion).arg(imports.at(j).minversion));
+                            }
+                            errors->prepend(error);
+                        }
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+    if (errors) {
+        QQmlError error;
+        if (typeRecursionDetected)
+            error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
+        else
+            error.setDescription(QQmlImportDatabase::tr("is not a type"));
+        errors->prepend(error);
+    }
+    return false;
+}
+
+QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
+: ref(1), typeLoader(loader) {
 }
 
 QQmlImportsPrivate::~QQmlImportsPrivate()
 {
-    foreach (QQmlImportedNamespace* s, set.values())
+    foreach (QQmlImportNamespace* s, set.values())
         delete s;
 }
 
-bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const QString &uri,
-                                                 QQmlImportDatabase *database,
-                                                 QQmlDirComponents* components,
-                                                 QQmlDirScripts* scripts,
-                                                 QList<QQmlError> *errors)
+/*!
+Import an extension defined by a qmldir file.
+
+\a qmldirFilePath is either a raw file path, or a bundle url.
+
+This call will modify the \a url parameter if importing the extension redirects to
+a bundle path.
+*/
+bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath,
+                                         const QString &uri,
+                                         QQmlImportDatabase *database,
+                                         QQmlDirComponents* components,
+                                         QQmlDirScripts* scripts,
+                                         QString *url,
+                                         QList<QQmlError> *errors)
 {
-    const QQmlDirParser *qmldirParser = typeLoader->qmlDirParser(absoluteFilePath);
+    // As qmldirFilePath is always local, this method can always return synchronously
+    const QQmlDirParser *qmldirParser = typeLoader->qmlDirParser(qmldirFilePath, uri, url);
     if (qmldirParser->hasError()) {
         if (errors) {
+            QUrl url;
+
+            if (QQmlFile::isBundle(qmldirFilePath))
+                url = QUrl(qmldirFilePath);
+            else
+                url = QUrl::fromLocalFile(qmldirFilePath);
+
             const QList<QQmlError> qmldirErrors = qmldirParser->errors(uri);
-            for (int i = 0; i < qmldirErrors.size(); ++i)
-                errors->prepend(qmldirErrors.at(i));
+            for (int i = 0; i < qmldirErrors.size(); ++i) {
+                QQmlError error = qmldirErrors.at(i);
+                error.setUrl(url);
+                errors->append(error);
+            }
         }
         return false;
     }
 
     if (qmlImportTrace())
         qDebug().nospace() << "QQmlImports(" << qPrintable(base) << "::importExtension: "
-                           << "loaded " << absoluteFilePath;
+                           << "loaded " << qmldirFilePath;
+
+    if (!qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(qmldirFilePath)) {
+        qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(qmldirFilePath);
 
-    if (! qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(absoluteFilePath)) {
-        qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(absoluteFilePath);
+        QString qmldirPath = qmldirFilePath;
 
-        QString qmldirPath = absoluteFilePath;
-        int slash = absoluteFilePath.lastIndexOf(QLatin1Char('/'));
+        if (QQmlFile::isBundle(*url))
+            qmldirPath = QQmlFile::bundleFileName(*url, typeLoader->engine());
+
+        int slash = qmldirFilePath.lastIndexOf(QLatin1Char('/'));
         if (slash > 0)
             qmldirPath.truncate(slash);
-        foreach (const QQmlDirParser::Plugin &plugin, qmldirParser->plugins()) {
 
-            QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name);
+        foreach (const QQmlDirParser::Plugin &plugin, qmldirParser->plugins()) {
+            QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath,
+                                                               plugin.path, plugin.name);
             if (!resolvedFilePath.isEmpty()) {
                 if (!database->importPlugin(resolvedFilePath, uri, errors)) {
                     if (errors) {
@@ -466,8 +614,8 @@ bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const
                         // The reason is that the lower level may add url and line/column numbering information.
                         QQmlError poppedError = errors->takeFirst();
                         QQmlError error;
-                        error.setDescription(QQmlImportDatabase::tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description()));
-                        error.setUrl(QUrl::fromLocalFile(absoluteFilePath));
+                        error.setDescription(tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description()));
+                        error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
                         errors->prepend(error);
                     }
                     return false;
@@ -475,8 +623,8 @@ bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const
             } else {
                 if (errors) {
                     QQmlError error;
-                    error.setDescription(QQmlImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name));
-                    error.setUrl(QUrl::fromLocalFile(absoluteFilePath));
+                    error.setDescription(tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name));
+                    error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
                     errors->prepend(error);
                 }
                 return false;
@@ -494,12 +642,16 @@ bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const
 
 QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
 {
+    struct I { static bool greaterThan(const QString &s1, const QString &s2) {
+        return s1 > s2;
+    } };
+
     QString dir = dir_arg;
     if (dir.endsWith(QLatin1Char('/')) || dir.endsWith(QLatin1Char('\\')))
         dir.chop(1);
 
     QStringList paths = database->fileImportPath;
-    qSort(paths.begin(), paths.end(), greaterThan); // Ensure subdirs preceed their parents.
+    qSort(paths.begin(), paths.end(), I::greaterThan); // Ensure subdirs preceed their parents.
 
     QString stableRelativePath = dir;
     foreach(const QString &path, paths) {
@@ -520,208 +672,285 @@ QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDataba
     }
 
     stableRelativePath.replace(QLatin1Char('/'), QLatin1Char('.'));
+
     return stableRelativePath;
 }
 
-QString QQmlImportsPrivate::add(const QQmlDirComponents &qmldircomponentsnetwork,
-                                     const QString& uri_arg, const QString& prefix, int vmaj, int vmin, 
-                                     QQmlScript::Import::Type importType, 
-                                     QQmlImportDatabase *database, QList<QQmlError> *errors)
+/*
+Locates the qmldir file for \a uri version \a vmaj.vmin.  Returns true if found,
+and fills in outQmldirFilePath and outQmldirUrl appropriately.  Otherwise returns
+false.
+*/
+bool QQmlImportsPrivate::locateQmldir(const QString &uri, int vmaj, int vmin,
+                                      QQmlImportDatabase *database,
+                                      QString *outQmldirFilePath,
+                                      QString *outQmldirPathUrl)
 {
-    static QLatin1String Slash_qmldir("/qmldir");
-    static QLatin1Char Slash('/');
-
-    QQmlDirComponents qmldircomponents = qmldircomponentsnetwork;
-    QQmlDirScripts qmldirscripts;
-    QString uri = uri_arg;
-    QQmlImportedNamespace *s;
-    if (prefix.isEmpty()) {
-        s = &unqualifiedset;
-    } else {
-        s = set.value(prefix);
-        if (!s)
-            set.insert(prefix,(s=new QQmlImportedNamespace));
+    Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
+
+    // Check cache first
+
+    QQmlImportDatabase::QmldirCache *cacheHead = 0;
+    {
+    QQmlImportDatabase::QmldirCache **cachePtr = database->qmldirCache.value(uri);
+    if (cachePtr) {
+        cacheHead = *cachePtr;
+        QQmlImportDatabase::QmldirCache *cache = cacheHead;
+        while (cache) {
+            if (cache->versionMajor == vmaj && cache->versionMinor == vmin) {
+                *outQmldirFilePath = cache->qmldirFilePath;
+                *outQmldirPathUrl = cache->qmldirPathUrl;
+                return !cache->qmldirFilePath.isEmpty();
+            }
+            cache = cache->next;
+        }
+    }
     }
-    QString url = uri;
-    bool versionFound = false;
-    if (importType == QQmlScript::Import::Library) {
 
-        Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
+    static QLatin1Char Slash('/');
+    static QLatin1String Slash_qmldir("/qmldir");
 
-        url.replace(QLatin1Char('.'), Slash);
-        bool found = false;
-        QString dir;
-        QString qmldir;
+    QString url = uri;
+    url.replace(QLatin1Char('.'), Slash);
 
-        // step 1: search for extension with fully encoded version number
+    // step 0: search for extension with fully encoded version number (eg. MyModule.3.2)
+    // step 1: search for extension with encoded version major (eg. MyModule.3)
+    // step 2: search for extension without version number (eg. MyModule)
+    for (int step = 0; step <= 2; ++step) {
         foreach (const QString &p, database->fileImportPath) {
-            dir = p+Slash+url;
-
-            QFileInfo fi(dir+QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin)+QLatin1String("/qmldir"));
-            const QString absoluteFilePath = fi.absoluteFilePath();
+            QString dir = p + Slash + url;
 
-            if (fi.isFile()) {
-                found = true;
+            QString qmldirFile = dir;
+            if (step == 0) qmldirFile += QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin);
+            else if (step == 1) qmldirFile += QString(QLatin1String(".%1")).arg(vmaj);
+            qmldirFile += Slash_qmldir;
 
-                const QString absolutePath = fi.absolutePath();
+            QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader;
+            QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirFile);
+            if (!absoluteFilePath.isEmpty()) {
+                QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1);
                 if (absolutePath.at(0) == QLatin1Char(':'))
                     url = QLatin1String("qrc://") + absolutePath.mid(1);
                 else
-                    url = QUrl::fromLocalFile(fi.absolutePath()).toString();
-                uri = resolvedUri(dir, database);
-                if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
-                    return QString();
-                break;
+                    url = QUrl::fromLocalFile(absolutePath).toString();
+
+                QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
+                cache->versionMajor = vmaj;
+                cache->versionMinor = vmin;
+                cache->qmldirFilePath = absoluteFilePath;
+                cache->qmldirPathUrl = url;
+                cache->next = cacheHead;
+                database->qmldirCache.insert(uri, cache);
+
+                *outQmldirFilePath = absoluteFilePath;
+                *outQmldirPathUrl = url;
+
+                return true;
             }
         }
+    }
 
-        if (!found) {
-            // step 2: search for extension with encoded version major
-            foreach (const QString &p, database->fileImportPath) {
-                dir = p+Slash+url;
-
-                QFileInfo fi(dir+QString(QLatin1String(".%1")).arg(vmaj)+QLatin1String("/qmldir"));
-                const QString absoluteFilePath = fi.absoluteFilePath();
-
-                if (fi.isFile()) {
-                    found = true;
-
-                    const QString absolutePath = fi.absolutePath();
-                    if (absolutePath.at(0) == QLatin1Char(':'))
-                        url = QLatin1String("qrc://") + absolutePath.mid(1);
-                    else
-                        url = QUrl::fromLocalFile(fi.absolutePath()).toString();
-                    uri = resolvedUri(dir, database);
-                    if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
-                        return QString();
-                    break;
-                }
-            }
+    QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
+    cache->versionMajor = vmaj;
+    cache->versionMinor = vmin;
+    cache->next = cacheHead;
+    database->qmldirCache.insert(uri, cache);
+
+    return false;
+}
+
+bool QQmlImportsPrivate::addImport(const QQmlDirComponents &qmldircomponentsnetwork,
+                                   const QString& importedUri, const QString& prefix,
+                                   int vmaj, int vmin, QQmlScript::Import::Type importType,
+                                   bool isImplicitImport, QQmlImportDatabase *database,
+                                   QString *outUrl, QList<QQmlError> *errors)
+{
+    Q_ASSERT(errors);
+    Q_ASSERT(importType == QQmlScript::Import::File || importType == QQmlScript::Import::Library);
+
+    static QLatin1String String_qmldir("qmldir");
+    static QLatin1String Slash_qmldir("/qmldir");
+    static QLatin1Char Slash('/');
+
+    // The list of components defined by a qmldir file for this import.
+    QQmlDirComponents qmldircomponents;
+    // The list of scripts defined by a qmldir file for this import.
+    QQmlDirScripts qmldirscripts;
+    // The namespace that this import affects.
+    QQmlImportNamespace *importSet = 0;
+    // The uri for this import.  For library imports this is the same as importedUri
+    // specified by the user, but it may be different in the case of file imports.
+    QString uri;
+    // The url for the path containing files for this import.
+    QString url;
+
+    qmldircomponents = qmldircomponentsnetwork;
+    uri = importedUri;
+    if (prefix.isEmpty()) {
+        importSet = &unqualifiedset;
+    } else {
+        importSet = set.value(prefix);
+        if (!importSet) {
+            importSet = new QQmlImportNamespace;
+            set.insert(prefix, importSet);
         }
+    }
+
+    if (importType == QQmlScript::Import::Library) {
+        Q_ASSERT(vmaj >= 0 && vmin >= 0);
+
+        QString qmldirFilePath;
+
+        if (locateQmldir(uri, vmaj, vmin, database, &qmldirFilePath, &url)) {
+
+            if (!importExtension(qmldirFilePath, uri, database, &qmldircomponents,
+                                 &qmldirscripts, &url, errors))
+                return false;
 
-        if (!found) {
-            // step 3: search for extension without version number
-
-            foreach (const QString &p, database->fileImportPath) {
-                dir = p+Slash+url;
-                qmldir = dir+Slash_qmldir;
-
-                QString absoluteFilePath = typeLoader->absoluteFilePath(qmldir);
-                if (!absoluteFilePath.isEmpty()) {
-                    found = true;
-                    QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1);
-                    if (absolutePath.at(0) == QLatin1Char(':'))
-                        url = QLatin1String("qrc://") + absolutePath.mid(1);
-                    else
-                        url = QUrl::fromLocalFile(absolutePath).toString();
-                    uri = resolvedUri(dir, database);
-                    if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
-                        return QString();
-                    break;
-                }
-            }
         }
 
-        if (QQmlMetaType::isModule(uri, vmaj, vmin))
-            versionFound = true;
+        if (!QQmlMetaType::isModule(uri, vmaj, vmin)) {
 
-        if (!versionFound && qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) {
-            if (errors) {
-                QQmlError error; // we don't set the url or line or column as these will be set by the loader.
+            if (qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) {
+                QQmlError error;
                 if (QQmlMetaType::isAnyModule(uri))
-                    error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin));
+                    error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin));
                 else
-                    error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri_arg));
+                    error.setDescription(tr("module \"%1\" is not installed").arg(importedUri));
                 errors->prepend(error);
+                return false;
+            } else {
+                int lowest_min = INT_MAX;
+                int highest_min = INT_MIN;
+                typedef QList<QQmlDirParser::Component>::const_iterator ConstIterator;
+                typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
+
+                for (ConstIterator cit = qmldircomponents.constBegin();
+                     cit != qmldircomponents.constEnd(); ++cit) {
+                    if (cit->majorVersion == vmaj) {
+                        lowest_min = qMin(lowest_min, cit->minorVersion);
+                        highest_min = qMax(highest_min, cit->minorVersion);
+                    }
+                }
+
+                for (SConstIterator cit = qmldirscripts.constBegin();
+                     cit != qmldirscripts.constEnd(); ++cit) {
+                    if (cit->majorVersion == vmaj) {
+                        lowest_min = qMin(lowest_min, cit->minorVersion);
+                        highest_min = qMax(highest_min, cit->minorVersion);
+                    }
+                }
+
+                if (lowest_min > vmin || highest_min < vmin) {
+                    QQmlError error;
+                    error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin));
+                    errors->prepend(error);
+                    return false;
+                }
             }
-            return QString();
+
         }
     } else {
-        if (importType == QQmlScript::Import::File && qmldircomponents.isEmpty()) {
-            QString importUrl = resolveLocalUrl(base, uri + Slash_qmldir);
-            QString localFileOrQrc = QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl);
-            if (!localFileOrQrc.isEmpty()) {
-                QString dir = QQmlEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
-                if (!typeLoader->directoryExists(dir)) {
-                    if (errors) {
-                        QQmlError error; // we don't set the line or column as these will be set by the loader.
-                        error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri_arg));
-                        error.setUrl(QUrl(importUrl));
+
+        Q_ASSERT(importType == QQmlScript::Import::File);
+
+        if (qmldircomponents.isEmpty()) {
+
+            QString qmldirPath = uri;
+            if (uri.endsWith(Slash)) qmldirPath += String_qmldir;
+            else qmldirPath += Slash_qmldir;
+            QString qmldirUrl = resolveLocalUrl(base, qmldirPath);
+
+            if (QQmlFile::isBundle(qmldirUrl)) {
+
+                QString dir = resolveLocalUrl(base, uri);
+                Q_ASSERT(QQmlFile::isBundle(dir));
+                if (!QQmlFile::bundleDirectoryExists(dir, typeLoader->engine())) {
+                    if (!isImplicitImport) {
+                        QQmlError error;
+                        error.setDescription(tr("\"%1\": no such directory").arg(importedUri));
+                        error.setUrl(QUrl(qmldirUrl));
                         errors->prepend(error);
                     }
-                    return QString(); // local import dirs must exist
+                    return false;
                 }
+
+                // Transforms the (possible relative) uri into our best guess relative to the
+                // import paths.
                 uri = resolvedUri(dir, database);
+
                 if (uri.endsWith(Slash))
                     uri.chop(1);
-                if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
-                    if (!importExtension(localFileOrQrc,uri,database,&qmldircomponents,&qmldirscripts,errors))
-                        return QString();
+                if (QQmlFile::bundleFileExists(qmldirUrl, typeLoader->engine())) {
+                    if (!importExtension(qmldirUrl, uri, database, &qmldircomponents,
+                                         &qmldirscripts, &url, errors))
+                        return false;
                 }
-            } else {
-                if (prefix.isEmpty()) {
-                    // directory must at least exist for valid import
-                    QString localFileOrQrc = QQmlEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
-                    if (!typeLoader->directoryExists(localFileOrQrc)) {
-                        if (errors) {
-                            QQmlError error; // we don't set the line or column as these will be set by the loader.
-                            if (localFileOrQrc.isEmpty())
-                                error.setDescription(QQmlImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(uri));
-                            else
-                                error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri));
-                            error.setUrl(QUrl(importUrl));
-                            errors->prepend(error);
-                        }
-                        return QString();
+
+            } else if (QQmlFile::isLocalFile(qmldirUrl)) {
+
+                QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
+                Q_ASSERT(!localFileOrQrc.isEmpty());
+
+                QString dir = QQmlFile::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
+                if (!typeLoader->directoryExists(dir)) {
+                    if (!isImplicitImport) {
+                        QQmlError error;
+                        error.setDescription(tr("\"%1\": no such directory").arg(importedUri));
+                        error.setUrl(QUrl(qmldirUrl));
+                        errors->prepend(error);
                     }
+                    return false;
                 }
-            }
-        }
 
-        url = resolveLocalUrl(base, url);
-    }
+                // Transforms the (possible relative) uri into our best guess relative to the
+                // import paths.
+                uri = resolvedUri(dir, database);
+
+                if (uri.endsWith(Slash))
+                    uri.chop(1);
+                if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
+                    if (!importExtension(localFileOrQrc, uri, database, &qmldircomponents,
+                                         &qmldirscripts, &url, errors))
+                        return false;
+                }
 
-    if (!versionFound && (vmaj > -1) && (vmin > -1) && !qmldircomponents.isEmpty()) {
-        int lowest_min = INT_MAX;
-        int highest_min = INT_MIN;
+            } else if (prefix.isEmpty()) {
 
-        QList<QQmlDirParser::Component>::const_iterator cend = qmldircomponents.constEnd();
-        for (QList<QQmlDirParser::Component>::const_iterator cit = qmldircomponents.constBegin(); cit != cend; ++cit) {
-            if (cit->majorVersion == vmaj) {
-                lowest_min = qMin(lowest_min, cit->minorVersion);
-                highest_min = qMax(highest_min, cit->minorVersion);
-            }
-        }
+                if (!isImplicitImport) {
+                    QQmlError error;
+                    error.setDescription(tr("import \"%1\" has no qmldir and no namespace").arg(uri));
+                    error.setUrl(QUrl(qmldirUrl));
+                    errors->prepend(error);
+                }
+
+                return false;
 
-        if (lowest_min > vmin || highest_min < vmin) {
-            if (errors) {
-                QQmlError error; // we don't set the url or line or column information, as these will be set by the loader.
-                error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin));
-                errors->prepend(error);
             }
-            return QString();
         }
+
+        url = resolveLocalUrl(base, importedUri);
+        if (!url.endsWith(Slash))
+            url += Slash;
     }
 
-    if (!url.endsWith(Slash))
-        url += Slash;
+    Q_ASSERT(url.isEmpty() || url.endsWith(Slash));
 
     QMap<QString, QQmlDirParser::Script> scripts;
-
     if (!qmldirscripts.isEmpty()) {
         // Verify that we haven't imported these scripts already
-        QList<QQmlImportedNamespace::Data>::const_iterator end = s->imports.constEnd();
-        for (QList<QQmlImportedNamespace::Data>::const_iterator it = s->imports.constBegin(); it != end; ++it) {
+        for (QList<QQmlImportNamespace::Import>::const_iterator it = importSet->imports.constBegin();
+             it != importSet->imports.constEnd(); ++it) {
             if (it->uri == uri) {
                 QQmlError error;
-                error.setDescription(QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url));
+                error.setDescription(tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url));
                 errors->prepend(error);
-                return QString();
+                return false;
             }
         }
 
-        QList<QQmlDirParser::Script>::const_iterator send = qmldirscripts.constEnd();
-        for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin(); sit != send; ++sit) {
+        for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
+             sit != qmldirscripts.constEnd(); ++sit) {
             // Only include scripts that match our requested version
             if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
                 ((vmin == -1) || (sit->minorVersion <= vmin))) {
@@ -735,124 +964,80 @@ QString QQmlImportsPrivate::add(const QQmlDirComponents &qmldircomponentsnetwork
         }
     }
 
-    QQmlImportedNamespace::Data data;
-    data.uri = uri;
-    data.url = url;
-    data.majversion = vmaj;
-    data.minversion = vmin;
-    data.isLibrary = importType == QQmlScript::Import::Library;
-    data.qmlDirComponents = qmldircomponents;
-    data.qmlDirScripts = scripts.values();
+    QQmlImportNamespace::Import import;
+    import.uri = uri;
+    import.url = url;
+    import.majversion = vmaj;
+    import.minversion = vmin;
+    import.isLibrary = importType == QQmlScript::Import::Library;
+    import.qmlDirComponents = qmldircomponents;
+    import.qmlDirScripts = scripts.values();
+
+    importSet->imports.prepend(import);
 
-    s->imports.prepend(data);
+    if (outUrl) *outUrl = url;
 
-    return data.url;
+    return true;
 }
 
-bool QQmlImportsPrivate::find(const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
-                                      QString* url_return, QList<QQmlError> *errors)
+/*!
+  \internal
+
+  Adds an implicit "." file import.  This is equivalent to calling addImport(), but error
+  messages related to the path or qmldir file not existing are suppressed.
+*/
+bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb,
+                                    const QQmlDirComponents &qmldircomponentsnetwork,
+                                    QList<QQmlError> *errors)
 {
-    QQmlImportedNamespace *s = 0;
-    int slash = type.indexOf(QLatin1Char('/'));
-    if (slash >= 0) {
-        QString namespaceName = type.left(slash);
-        s = set.value(namespaceName);
-        if (!s) {
-            if (errors) {
-                QQmlError error;
-                error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName));
-                errors->prepend(error);
-            }
-            return false;
-        }
-        int nslash = type.indexOf(QLatin1Char('/'),slash+1);
-        if (nslash > 0) {
-            if (errors) {
-                QQmlError error;
-                error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
-                errors->prepend(error);
-            }
-            return false;
-        }
-    } else {
-        s = &unqualifiedset;
-    }
-    QString unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower)
-    if (s) {
-        if (s->find(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors))
-            return true;
-        if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) {
-            // qualified, and only 1 url
-            *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype + QLatin1String(".qml"));
-            return true;
-        }
-    }
+    Q_ASSERT(errors);
 
-    return false;
-}
+    if (qmlImportTrace())
+        qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString())
+                           << ")::addImplicitImport";
 
-QQmlImportedNamespace *QQmlImportsPrivate::findNamespace(const QString& type)
-{
-    return set.value(type);
+
+    return d->addImport(qmldircomponentsnetwork, QLatin1String("."), QString(), -1, -1,
+                        QQmlScript::Import::File, true, importDb, 0, errors);
 }
 
-bool QQmlImportedNamespace::find(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
-          QString* url_return, QString *base, QList<QQmlError> *errors)
+/*!
+  \internal
+
+  Adds information to \a imports such that subsequent calls to resolveType()
+  will resolve types qualified by \a prefix by considering types found at the given \a uri.
+
+  The uri is either a directory (if importType is FileImport), or a URI resolved using paths
+  added via addImportPath() (if importType is LibraryImport).
+
+  The \a prefix may be empty, in which case the import location is considered for
+  unqualified types.
+
+  The base URL must already have been set with Import::setBaseUrl().
+
+  Optionally, the url the import resolved to can be returned by providing the url parameter.
+  Not all imports will result in an output url being generated, in which case the url will
+  be set to an empty string.
+
+  Returns true on success, and false on failure.  In case of failure, the errors array will
+  filled appropriately.
+*/
+bool QQmlImports::addImport(QQmlImportDatabase *importDb,
+                            const QString& uri, const QString& prefix, int vmaj, int vmin,
+                            QQmlScript::Import::Type importType,
+                            const QQmlDirComponents &qmldircomponentsnetwork,
+                            QString *url, QList<QQmlError> *errors)
 {
-    bool typeRecursionDetected = false;
-    for (int i=0; i<imports.count(); ++i) {
-        if (find_helper(typeLoader, imports.at(i), type, vmajor, vminor, type_return, url_return, base, &typeRecursionDetected)) {
-            if (qmlCheckTypes()) {
-                // check for type clashes
-                for (int j = i+1; j<imports.count(); ++j) {
-                    if (find_helper(typeLoader, imports.at(j), type, vmajor, vminor, 0, 0, base)) {
-                        if (errors) {
-                            QString u1 = imports.at(i).url;
-                            QString u2 = imports.at(j).url;
-                            if (base) {
-                                QString b = *base;
-                                int slash = b.lastIndexOf(QLatin1Char('/'));
-                                if (slash >= 0) {
-                                    b = b.left(slash+1);
-                                    QString l = b.left(slash);
-                                    if (u1.startsWith(b))
-                                        u1 = u1.mid(b.count());
-                                    else if (u1 == l)
-                                        u1 = QQmlImportDatabase::tr("local directory");
-                                    if (u2.startsWith(b))
-                                        u2 = u2.mid(b.count());
-                                    else if (u2 == l)
-                                        u2 = QQmlImportDatabase::tr("local directory");
-                                }
-                            }
+    Q_ASSERT(errors);
 
-                            QQmlError error;
-                            if (u1 != u2) {
-                                error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
-                            } else {
-                                error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
-                                                        .arg(u1)
-                                                        .arg(imports.at(i).majversion).arg(imports.at(i).minversion)
-                                                        .arg(imports.at(j).majversion).arg(imports.at(j).minversion));
-                            }
-                            errors->prepend(error);
-                        }
-                        return false;
-                    }
-                }
-            }
-            return true;
-        }
-    }
-    if (errors) {
-        QQmlError error;
-        if (typeRecursionDetected)
-            error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
-        else
-            error.setDescription(QQmlImportDatabase::tr("is not a type"));
-        errors->prepend(error);
-    }
-    return false;
+    if (qmlImportTrace())
+        qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: "
+                           << uri << " " << vmaj << '.' << vmin << " "
+                           << (importType==QQmlScript::Import::Library? "Library" : "File")
+                           << " as " << prefix;
+
+    return d->addImport(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, false,
+                        importDb, url, errors);
 }
 
 /*!
@@ -890,37 +1075,16 @@ QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
 
 QQmlImportDatabase::~QQmlImportDatabase()
 {
-}
-
-/*!
-  \internal
-
-  Adds information to \a imports such that subsequent calls to resolveType()
-  will resolve types qualified by \a prefix by considering types found at the given \a uri.
-
-  The uri is either a directory (if importType is FileImport), or a URI resolved using paths
-  added via addImportPath() (if importType is LibraryImport).
-
-  The \a prefix may be empty, in which case the import location is considered for
-  unqualified types.
-
-  Returns the resolved URL of the import on success.
-
-  The base URL must already have been set with Import::setBaseUrl().
-*/
-QString QQmlImports::addImport(QQmlImportDatabase *importDb,
-                                    const QString& uri, const QString& prefix, int vmaj, int vmin, 
-                                    QQmlScript::Import::Type importType, 
-                                    const QQmlDirComponents &qmldircomponentsnetwork, 
-                                    QList<QQmlError> *errors)
-{
-    if (qmlImportTrace())
-        qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: " 
-                           << uri << " " << vmaj << '.' << vmin << " " 
-                           << (importType==QQmlScript::Import::Library? "Library" : "File") 
-                           << " as " << prefix;
-
-    return d->add(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, importDb, errors);
+    for (QStringHash<QmldirCache *>::ConstIterator iter = qmldirCache.begin();
+         iter != qmldirCache.end(); ++iter) {
+
+        QmldirCache *c = *iter;
+        while (c) {
+            QmldirCache *n = c->next;
+            delete c;
+            c = n;
+        }
+    }
 }
 
 /*!
@@ -932,9 +1096,10 @@ QString QQmlImports::addImport(QQmlImportDatabase *importDb,
   \a qmldirPath is the location of the qmldir file.
  */
 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
-                                                  const QString &qmldirPath, const QString &qmldirPluginPath,
-                                                  const QString &baseName, const QStringList &suffixes,
-                                                  const QString &prefix)
+                                          const QString &qmldirPath,
+                                          const QString &qmldirPluginPath,
+                                          const QString &baseName, const QStringList &suffixes,
+                                          const QString &prefix)
 {
     QStringList searchPaths = filePluginPath;
     bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
index 422f242..d673b64 100644 (file)
@@ -66,7 +66,7 @@ QT_BEGIN_NAMESPACE
 class QQmlTypeNameCache;
 class QQmlEngine;
 class QDir;
-class QQmlImportedNamespace;
+class QQmlImportNamespace;
 class QQmlImportsPrivate;
 class QQmlImportDatabase;
 class QQmlTypeLoader;
@@ -86,18 +86,22 @@ public:
     bool resolveType(const QString& type,
                      QQmlType** type_return, QString* url_return,
                      int *version_major, int *version_minor,
-                     QQmlImportedNamespace** ns_return,
+                     QQmlImportNamespace** ns_return,
                      QList<QQmlError> *errors = 0) const;
-    bool resolveType(QQmlImportedNamespace*, 
+    bool resolveType(QQmlImportNamespace*,
                      const QString& type,
                      QQmlType** type_return, QString* url_return,
                      int *version_major, int *version_minor) const;
 
-    QString addImport(QQmlImportDatabase *,
+    bool addImplicitImport(QQmlImportDatabase *importDb,
+                           const QQmlDirComponents &qmldircomponentsnetwork,
+                           QList<QQmlError> *errors);
+
+    bool addImport(QQmlImportDatabase *,
                    const QString& uri, const QString& prefix, int vmaj, int vmin, 
                    QQmlScript::Import::Type importType,
                    const QQmlDirComponents &qmldircomponentsnetwork, 
-                   QList<QQmlError> *errors);
+                   QString *url, QList<QQmlError> *errors);
 
     void populateCache(QQmlTypeNameCache *cache, QQmlEngine *) const;
 
@@ -142,6 +146,16 @@ private:
                           const QString &qmldirPath, const QString &qmldirPluginPath,
                           const QString &baseName);
 
+    struct QmldirCache {
+        int versionMajor;
+        int versionMinor;
+        QString qmldirFilePath;
+        QString qmldirPathUrl;
+        QmldirCache *next;
+    };
+    // Maps from an import to a linked list of qmldir info.
+    // Used in QQmlImportsPrivate::locateQmldir()
+    QStringHash<QmldirCache *> qmldirCache;
 
     // XXX thread
     QStringList filePluginPath;
index 05882f3..b1a50a4 100644 (file)
@@ -451,22 +451,6 @@ QStringList QQmlScript::Variant::asStringList() const
 //
 // Actual parser classes
 //
-void QQmlScript::Import::extractVersion(int *maj, int *min) const
-{
-    *maj = -1; *min = -1;
-
-    if (!version.isEmpty()) {
-        int dot = version.indexOf(QLatin1Char('.'));
-        if (dot < 0) {
-            *maj = version.toInt();
-            *min = 0;
-        } else {
-            *maj = version.left(dot).toInt();
-            *min = version.mid(dot+1).toInt();
-        }
-    }
-}
-
 namespace {
 
 class ProcessAST: protected AST::Visitor
@@ -526,6 +510,8 @@ public:
 
     void operator()(const QString &code, AST::Node *node);
 
+    static void extractVersion(QStringRef string, int *maj, int *min);
+
 protected:
 
     QQmlScript::Object *defineObjectBinding(AST::UiQualifiedId *propertyName, bool onAssignment,
@@ -661,6 +647,26 @@ QString ProcessAST::qualifiedNameId() const
     return _scope.join(QLatin1String("/"));
 }
 
+void ProcessAST::extractVersion(QStringRef string, int *maj, int *min)
+{
+    *maj = -1; *min = -1;
+
+    if (!string.isEmpty()) {
+
+        int dot = string.indexOf(QLatin1Char('.'));
+
+        if (dot < 0) {
+            *maj = string.toString().toInt();
+            *min = 0;
+        } else {
+            const QString *s = string.string();
+            int p = string.position();
+            *maj = QStringRef(s, p, dot).toString().toInt();
+            *min = QStringRef(s, p + dot + 1, string.size() - dot - 1).toString().toInt();
+        }
+    }
+}
+
 QString ProcessAST::asString(AST::UiQualifiedId *node) const
 {
     QString s;
@@ -898,7 +904,7 @@ bool ProcessAST::visit(AST::UiImport *node)
     }
 
     if (node->versionToken.isValid()) {
-        import.version = textAt(node->versionToken);
+        extractVersion(textRefAt(node->versionToken), &import.majorVersion, &import.minorVersion);
     } else if (import.type == QQmlScript::Import::Library) {
         QQmlError error;
         error.setDescription(QCoreApplication::translate("QQmlParser","Library import requires a version"));
@@ -1290,8 +1296,13 @@ public:
 };
 }
 
-bool QQmlScript::Parser::parse(const QByteArray &qmldata, const QUrl &url,
-                                       const QString &urlString)
+QByteArray QQmlScript::Parser::preparseData() const
+{
+    return QByteArray();
+}
+
+bool QQmlScript::Parser::parse(const QString &qmlcode, const QByteArray &preparseData,
+                               const QUrl &url, const QString &urlString)
 {
     clear();
 
@@ -1302,11 +1313,7 @@ bool QQmlScript::Parser::parse(const QByteArray &qmldata, const QUrl &url,
         _scriptFile = urlString;
     }
 
-    QTextStream stream(qmldata, QIODevice::ReadOnly);
-#ifndef QT_NO_TEXTCODEC
-    stream.setCodec("UTF-8");
-#endif
-    QString *code = _pool.NewString(stream.readAll());
+    QString *code = _pool.NewString(qmlcode);
 
     data = new QQmlScript::ParserJsASTData(_scriptFile);
 
@@ -1573,7 +1580,6 @@ QQmlScript::Parser::JavaScriptMetaData QQmlScript::Parser::extractMetaData(QStri
             } else {
                 // URI
                 QString uri;
-                QString version;
 
                 while (true) {
                     if (!isUriToken(token))
@@ -1593,7 +1599,9 @@ QQmlScript::Parser::JavaScriptMetaData QQmlScript::Parser::extractMetaData(QStri
                 }
 
                 CHECK_TOKEN(T_NUMERIC_LITERAL);
-                version = script.mid(l.tokenOffset(), l.tokenLength());
+                int vmaj, vmin;
+                ProcessAST::extractVersion(QStringRef(&script, l.tokenOffset(), l.tokenLength()),
+                                           &vmaj, &vmin);
 
                 token = l.lex();
 
@@ -1624,7 +1632,8 @@ QQmlScript::Parser::JavaScriptMetaData QQmlScript::Parser::extractMetaData(QStri
                 Import import;
                 import.type = Import::Library;
                 import.uri = uri;
-                import.version = version;
+                import.majorVersion = vmaj;
+                import.minorVersion = vmin;
                 import.qualifier = importId;
                 import.location = location;
 
index 8705f2a..1e46e6b 100644 (file)
@@ -109,16 +109,16 @@ struct LocationSpan
 class Import
 {
 public:
-    Import() : type(Library) {}
+    Import() : type(Library), majorVersion(-1), minorVersion(-1) {}
 
     enum Type { Library, File, Script };
     Type type;
 
     QString uri;
     QString qualifier;
-    QString version;
 
-    void extractVersion(int *maj, int *min) const;
+    int majorVersion;
+    int minorVersion;
 
     QQmlScript::LocationSpan location;
 };
@@ -469,14 +469,16 @@ public:
 };
 
 class ParserJsASTData;
-class Q_AUTOTEST_EXPORT Parser
+class Q_QML_EXPORT Parser
 {
 public:
     Parser();
     ~Parser();
 
-    bool parse(const QByteArray &data, const QUrl &url = QUrl(),
-               const QString &urlString = QString());
+    bool parse(const QString &data, const QByteArray &preparseData,
+               const QUrl &url = QUrl(), const QString &urlString = QString());
+
+    QByteArray preparseData() const;
 
     QList<TypeReference*> referencedTypes() const;
 
index abe2c0b..2e8f17e 100644 (file)
 #include <QtCore/qdebug.h>
 #include <QtCore/qmutex.h>
 #include <QtCore/qthread.h>
+#include <QtQml/qqmlfile.h>
 #include <QtCore/qdiriterator.h>
-#include <QtCore/qwaitcondition.h>
 #include <QtQml/qqmlcomponent.h>
+#include <QtCore/qwaitcondition.h>
 #include <QtQml/qqmlextensioninterface.h>
 
 #if defined (Q_OS_UNIX)
@@ -92,6 +93,8 @@
 
 #endif
 
+DEFINE_BOOL_CONFIG_OPTION(dumpErrors, QML_DUMP_ERRORS);
+
 QT_BEGIN_NAMESPACE
 
 // This is a lame object that we need to ensure that slots connected to
@@ -453,6 +456,11 @@ void QQmlDataBlob::setError(const QList<QQmlError> &errors)
     m_errors = errors; // Must be set before the m_data fence
     m_data.setStatus(Error);
 
+    if (dumpErrors()) {
+        qWarning().nospace() << "Errors for " << m_finalUrl.toString();
+        for (int ii = 0; ii < errors.count(); ++ii)
+            qWarning().nospace() << "    " << qPrintable(errors.at(ii).toString());
+    }
     cancelAllWaitingFor();
 
     if (!m_inCallback)
@@ -486,7 +494,7 @@ void QQmlDataBlob::addDependency(QQmlDataBlob *blob)
 }
 
 /*!
-\fn void QQmlDataBlob::dataReceived(const QByteArray &data)
+\fn void QQmlDataBlob::dataReceived(const Data &data)
 
 Invoked when data for the blob is received.  Implementors should use this callback
 to determine a blob's dependencies.  Within this callback you may call setError()
@@ -1018,28 +1026,22 @@ void QQmlDataLoader::loadThread(QQmlDataBlob *blob)
         return;
     }
 
-    QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(blob->m_url);
+    if (QQmlFile::isSynchronous(blob->m_url)) {
+        QQmlFile file(m_engine, blob->m_url);
 
-    if (!lf.isEmpty()) {
-        if (!QQml_isFileCaseCorrect(lf)) {
+        if (file.isError()) {
             QQmlError error;
             error.setUrl(blob->m_url);
-            error.setDescription(QLatin1String("File name case mismatch"));
+            error.setDescription(file.error());
             blob->setError(error);
             return;
         }
-        QFile file(lf);
-        if (file.open(QFile::ReadOnly)) {
-            QByteArray data = file.readAll();
 
-            blob->m_data.setProgress(0xFF);
-            if (blob->m_data.isAsync())
-                m_thread->callDownloadProgressChanged(blob, 1.);
+        blob->m_data.setProgress(0xFF);
+        if (blob->m_data.isAsync())
+            m_thread->callDownloadProgressChanged(blob, 1.);
 
-            setData(blob, data);
-        } else {
-            blob->networkError(QNetworkReply::ContentNotFoundError);
-        }
+        setData(blob, &file);
 
     } else {
 
@@ -1138,9 +1140,23 @@ void QQmlDataLoader::initializeEngine(QQmlExtensionInterface *iface,
 
 void QQmlDataLoader::setData(QQmlDataBlob *blob, const QByteArray &data)
 {
+    QQmlDataBlob::Data d;
+    d.d = &data;
+    setData(blob, d);
+}
+
+void QQmlDataLoader::setData(QQmlDataBlob *blob, QQmlFile *file)
+{
+    QQmlDataBlob::Data d;
+    d.d = file;
+    setData(blob, d);
+}
+
+void QQmlDataLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::Data &d)
+{
     blob->m_inCallback = true;
 
-    blob->dataReceived(data);
+    blob->dataReceived(d);
 
     if (!blob->isError() && !blob->isWaiting())
         blob->allDependenciesDone();
@@ -1187,8 +1203,8 @@ Returns a QQmlTypeData for the specified \a url.  The QQmlTypeData may be cached
 QQmlTypeData *QQmlTypeLoader::get(const QUrl &url, Mode mode)
 {
     Q_ASSERT(!url.isRelative() && 
-            (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || 
-             !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
+            (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
+             !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
 
     lock();
     
@@ -1231,8 +1247,8 @@ Return a QQmlScriptBlob for \a url.  The QQmlScriptData may be cached.
 QQmlScriptBlob *QQmlTypeLoader::getScript(const QUrl &url)
 {
     Q_ASSERT(!url.isRelative() && 
-            (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || 
-             !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
+            (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
+             !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
 
     lock();
 
@@ -1257,8 +1273,8 @@ Returns a QQmlQmldirData for \a url.  The QQmlQmldirData may be cached.
 QQmlQmldirData *QQmlTypeLoader::getQmldir(const QUrl &url)
 {
     Q_ASSERT(!url.isRelative() && 
-            (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || 
-             !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
+            (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
+             !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
 
     lock();
 
@@ -1278,6 +1294,84 @@ QQmlQmldirData *QQmlTypeLoader::getQmldir(const QUrl &url)
 }
 
 /*!
+Returns a QQmlBundleData for \a identifier.
+*/
+QQmlBundleData *QQmlTypeLoader::getBundle(const QString &identifier)
+{
+    return getBundle(QHashedStringRef(identifier));
+}
+
+QQmlBundleData *QQmlTypeLoader::getBundle(const QHashedStringRef &identifier)
+{
+    lock();
+
+    QQmlBundleData *rv = 0;
+    QQmlBundleData **bundle = m_bundleCache.value(identifier);
+    if (bundle) {
+        rv = *bundle;
+        rv->addref();
+    }
+
+    unlock();
+
+    return rv;
+}
+
+QQmlBundleData::QQmlBundleData(const QString &file)
+: QQmlBundle(file), fileName(file)
+{
+}
+
+// XXX check for errors etc.
+void QQmlTypeLoader::addBundle(const QString &identifier, const QString &fileName)
+{
+    lock();
+    addBundleNoLock(identifier, fileName);
+    unlock();
+}
+
+void QQmlTypeLoader::addBundleNoLock(const QString &identifier, const QString &fileName)
+{
+    QQmlBundleData *data = new QQmlBundleData(fileName);
+    if (data->open()) {
+
+        m_bundleCache.insert(identifier, data);
+
+    } else {
+        data->release();
+    }
+}
+
+QString QQmlTypeLoader::bundleIdForQmldir(const QString &name, const QString &uriHint)
+{
+    lock();
+    QString *bundleId = m_qmldirBundleIdCache.value(name);
+    if (!bundleId) {
+        QString newBundleId = QLatin1String("qml.") + uriHint.toLower() /* XXX toLower()? */;
+        if (m_qmldirBundleIdCache.contains(newBundleId))
+            newBundleId += QString::number(m_qmldirBundleIdCache.count());
+        m_qmldirBundleIdCache.insert(name, newBundleId);
+        addBundleNoLock(newBundleId, name);
+        unlock();
+        return newBundleId;
+    } else {
+        unlock();
+        return *bundleId;
+    }
+}
+
+bool QQmlEngine::addNamedBundle(const QString &name, const QString &fileName)
+{
+    Q_D(QQmlEngine);
+
+    if (name.startsWith(QLatin1String("qml."))) // reserved
+        return false;
+
+    d->typeLoader.addBundle(name, fileName);
+    return true;
+}
+
+/*!
 Returns the absolute filename of path via a directory cache for files named
 "qmldir", "*.qml", "*.js", and plugins.
 Returns a empty string if the path does not exist.
@@ -1366,21 +1460,79 @@ bool QQmlTypeLoader::directoryExists(const QString &path)
 
 /*!
 Return a QQmlDirParser for absoluteFilePath.  The QQmlDirParser may be cached.
+
+\a filePath is either a bundle URL, or a local file path.
 */
-const QQmlDirParser *QQmlTypeLoader::qmlDirParser(const QString &absoluteFilePath)
+const QQmlDirParser *QQmlTypeLoader::qmlDirParser(const QString &filePath,
+                                                  const QString &uriHint,
+                                                  QString *outUrl)
 {
-    QQmlDirParser *qmldirParser;
-    QQmlDirParser **val = m_importQmlDirCache.value(absoluteFilePath);
+    DirParser *qmldirParser;
+    DirParser **val = m_importQmlDirCache.value(filePath);
     if (!val) {
-        qmldirParser = new QQmlDirParser;
-        qmldirParser->setFileSource(absoluteFilePath);
-        qmldirParser->setUrl(QUrl::fromLocalFile(absoluteFilePath));
-        qmldirParser->parse();
-        m_importQmlDirCache.insert(absoluteFilePath, qmldirParser);
+        qmldirParser = new DirParser;
+
+#define ERROR(description) { QQmlError e; e.setDescription(description); qmldirParser->setError(e); }
+#define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable"))
+#define CASE_MISMATCH_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File name case mismatch for \"%1\""))
+
+        if (QQmlFile::isBundle(filePath)) {
+
+            QUrl url(filePath);
+
+            QQmlFile file(engine(), url);
+            if (file.isError()) {
+                ERROR(NOT_READABLE_ERROR.arg(filePath));
+            } else {
+                qmldirParser->setSource(QString::fromUtf8(file.data(), file.size()));
+                qmldirParser->parse();
+            }
+
+        } else {
+
+            QFile file(filePath);
+            if (!QQml_isFileCaseCorrect(filePath)) {
+                ERROR(CASE_MISMATCH_ERROR.arg(filePath));
+            } else if (file.open(QFile::ReadOnly)) {
+                QByteArray data = file.read(QQmlBundle::bundleHeaderLength());
+
+                if (QQmlBundle::isBundleHeader(data.constData(), data.length())) {
+                    QString id = bundleIdForQmldir(filePath, uriHint);
+
+                    QString bundleUrl = QLatin1String("bundle://") + id + QLatin1String("/");
+                    qmldirParser->adjustedUrl = bundleUrl;
+
+                    QUrl url(bundleUrl + QLatin1String("qmldir"));
+
+                    QQmlFile file(engine(), url);
+                    if (file.isError()) {
+                        ERROR(NOT_READABLE_ERROR.arg(filePath));
+                    } else {
+                        qmldirParser->setSource(QString::fromUtf8(file.data(), file.size()));
+                        qmldirParser->parse();
+                    }
+                } else {
+                    data += file.readAll();
+                    qmldirParser->setSource(QString::fromUtf8(data));
+                    qmldirParser->parse();
+                }
+            } else {
+                ERROR(NOT_READABLE_ERROR.arg(filePath));
+            }
+
+        }
+
+#undef ERROR
+#undef NOT_READABLE_ERROR
+#undef CASE_MISMATCH_ERROR
+
+        m_importQmlDirCache.insert(filePath, qmldirParser);
     } else {
         qmldirParser = *val;
     }
 
+    if (!qmldirParser->adjustedUrl.isEmpty())
+        *outUrl = qmldirParser->adjustedUrl;
     return qmldirParser;
 }
 
@@ -1531,9 +1683,14 @@ void QQmlTypeData::completed()
     }
 }
 
-void QQmlTypeData::dataReceived(const QByteArray &data)
+void QQmlTypeData::dataReceived(const Data &data)
 {
-    if (!scriptParser.parse(data, finalUrl(), finalUrlString())) {
+    QString code = QString::fromUtf8(data.data(), data.size());
+    QByteArray preparseData;
+
+    if (data.isFile()) preparseData = data.asFile()->metaData(QLatin1String("qml:preparse"));
+
+    if (!scriptParser.parse(code, preparseData, finalUrl(), finalUrlString())) {
         setError(scriptParser.errors());
         return;
     }
@@ -1543,7 +1700,7 @@ void QQmlTypeData::dataReceived(const QByteArray &data)
     foreach (const QQmlScript::Import &import, scriptParser.imports()) {
         if (import.type == QQmlScript::Import::File && import.qualifier.isEmpty()) {
             QUrl importUrl = finalUrl().resolved(QUrl(import.uri + QLatin1String("/qmldir")));
-            if (QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) {
+            if (QQmlFile::urlToLocalFileOrQrc(importUrl).isEmpty()) {
                 QQmlQmldirData *data = typeLoader()->getQmldir(importUrl);
                 addDependency(data);
                 m_qmldirs << data;
@@ -1563,7 +1720,7 @@ void QQmlTypeData::dataReceived(const QByteArray &data)
 
     if (!finalUrl().scheme().isEmpty()) {
         QUrl importUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir")));
-        if (QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) {
+        if (QQmlFile::urlToLocalFileOrQrc(importUrl).isEmpty()) {
             QQmlQmldirData *data = typeLoader()->getQmldir(importUrl);
             addDependency(data);
             m_qmldirs << data;
@@ -1613,30 +1770,15 @@ void QQmlTypeData::resolveTypes()
     // For local urls, add an implicit import "." as first (most overridden) lookup. 
     // This will also trigger the loading of the qmldir and the import of any native 
     // types from available plugins.
-    QList<QQmlError> errors;
+    QList<QQmlError> implicitImportErrors;
     if (QQmlQmldirData *qmldir = qmldirForUrl(finalUrl().resolved(QUrl(QLatin1String("./qmldir"))))) {
-        m_imports.addImport(importDatabase, QLatin1String("."),
-                            QString(), -1, -1, QQmlScript::Import::File, 
-                            qmldir->dirComponents(), &errors);
+        m_imports.addImplicitImport(importDatabase, qmldir->dirComponents(), &implicitImportErrors);
     } else {
-        m_imports.addImport(importDatabase, QLatin1String("."), 
-                            QString(), -1, -1, QQmlScript::Import::File, 
-                            QQmlDirComponents(), &errors);
-    }
-
-    // remove any errors which are due to the implicit import which aren't real errors.
-    // for example, if the implicitly included qmldir file doesn't exist, that is not an error.
-    QList<QQmlError> realErrors;
-    for (int i = 0; i < errors.size(); ++i) {
-        if (errors.at(i).description() != QQmlImportDatabase::tr("import \".\" has no qmldir and no namespace")
-                && errors.at(i).description() != QQmlImportDatabase::tr("\".\": no such directory")) {
-            realErrors.prepend(errors.at(i)); // this is a real error.
-        }
+        m_imports.addImplicitImport(importDatabase, QQmlDirComponents(), &implicitImportErrors);
     }
 
-    // report any real errors which occurred during plugin loading or qmldir parsing.
-    if (!realErrors.isEmpty()) {
-        setError(realErrors);
+    if (!implicitImportErrors.isEmpty()) {
+        setError(implicitImportErrors);
         return;
     }
 
@@ -1651,13 +1793,10 @@ void QQmlTypeData::resolveTypes()
                 qmldircomponentsnetwork = qmldir->dirComponents();
         }
 
-        int vmaj = -1;
-        int vmin = -1;
-        import.extractVersion(&vmaj, &vmin);
-
         QList<QQmlError> errors;
-        if (m_imports.addImport(importDatabase, import.uri, import.qualifier, vmaj, vmin,
-                                import.type, qmldircomponentsnetwork, &errors).isNull()) {
+        if (!m_imports.addImport(importDatabase, import.uri, import.qualifier, import.majorVersion,
+                                 import.minorVersion, import.type, qmldircomponentsnetwork, 0,
+                                 &errors)) {
             QQmlError error;
             if (errors.size()) {
                 error = errors.takeFirst();
@@ -1703,7 +1842,7 @@ void QQmlTypeData::resolveTypes()
         QString url;
         int majorVersion;
         int minorVersion;
-        QQmlImportedNamespace *typeNamespace = 0;
+        QQmlImportNamespace *typeNamespace = 0;
         QList<QQmlError> errors;
 
         if (!m_imports.resolveType(parserRef->name, &ref.type, &url, &majorVersion, &minorVersion,
@@ -1824,12 +1963,12 @@ QQmlScriptData *QQmlScriptBlob::scriptData() const
     return m_scriptData;
 }
 
-void QQmlScriptBlob::dataReceived(const QByteArray &data)
+void QQmlScriptBlob::dataReceived(const Data &data)
 {
     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(m_typeLoader->engine());
     QQmlImportDatabase *importDatabase = &ep->importDatabase;
 
-    m_source = QString::fromUtf8(data);
+    m_source = QString::fromUtf8(data.data(), data.size());
 
     QQmlScript::Parser::JavaScriptMetaData metadata =
         QQmlScript::Parser::extractMetaData(m_source);
@@ -1853,14 +1992,11 @@ void QQmlScriptBlob::dataReceived(const QByteArray &data)
             m_scripts << ref;
         } else {
             Q_ASSERT(import.type == QQmlScript::Import::Library);
-            int vmaj = -1;
-            int vmin = -1;
-            import.extractVersion(&vmaj, &vmin);
-
             QList<QQmlError> errors;
-            QString importUrl = m_imports.addImport(importDatabase, import.uri, import.qualifier, vmaj, vmin,
-                                                    import.type, QQmlDirComponents(), &errors);
-            if (importUrl.isNull()) {
+            QString importUrl;
+            if (!m_imports.addImport(importDatabase, import.uri, import.qualifier, import.majorVersion,
+                                     import.minorVersion, import.type, QQmlDirComponents(), &importUrl,
+                                     &errors)) {
                 QQmlError error = errors.takeFirst();
                 // description should be set by addImport().
                 error.setUrl(m_imports.baseUrl());
@@ -1873,19 +2009,23 @@ void QQmlScriptBlob::dataReceived(const QByteArray &data)
             }
 
             // Does this library contain any scripts?
-            QUrl libraryUrl(importUrl);
-            const QQmlDirParser *dirParser = typeLoader()->qmlDirParser(libraryUrl.path() + QLatin1String("qmldir"));
-            foreach (const QQmlDirParser::Script &script, dirParser->scripts()) {
-                QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName));
-                QQmlScriptBlob *blob = typeLoader()->getScript(scriptUrl);
-                addDependency(blob);
-
-                ScriptReference ref;
-                ref.location = import.location.start;
-                ref.qualifier = script.nameSpace;
-                ref.nameSpace = import.qualifier;
-                ref.script = blob;
-                m_scripts << ref;
+            if (!importUrl.isEmpty()) {
+                QUrl libraryUrl(importUrl);
+                // XXX is this logic even correct???
+                QString adjustedUrl;
+                const QQmlDirParser *dirParser = typeLoader()->qmlDirParser(libraryUrl.path() + QLatin1String("qmldir"), QString(), &adjustedUrl);
+                foreach (const QQmlDirParser::Script &script, dirParser->scripts()) {
+                    QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName));
+                    QQmlScriptBlob *blob = typeLoader()->getScript(scriptUrl);
+                    addDependency(blob);
+
+                    ScriptReference ref;
+                    ref.location = import.location.start;
+                    ref.qualifier = script.nameSpace;
+                    ref.nameSpace = import.qualifier;
+                    ref.script = blob;
+                    m_scripts << ref;
+                }
             }
         }
     }
@@ -1951,10 +2091,10 @@ const QQmlDirComponents &QQmlQmldirData::dirComponents() const
     return m_components;
 }
 
-void QQmlQmldirData::dataReceived(const QByteArray &data)
+void QQmlQmldirData::dataReceived(const Data &data)
 {
     QQmlDirParser parser;
-    parser.setSource(QString::fromUtf8(data));
+    parser.setSource(QString::fromUtf8(data.data(), data.size()));
     parser.parse();
     m_components = parser.components();
 }
index 319b2e7..66f4fd5 100644 (file)
@@ -58,6 +58,7 @@
 #include <QtNetwork/qnetworkreply.h>
 #include <QtQml/qqmlerror.h>
 #include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlfile.h>
 
 #include <private/qv8_p.h>
 #include <private/qhashedstring_p.h>
@@ -65,6 +66,8 @@
 #include <private/qqmlimport_p.h>
 #include <private/qqmlcleanup_p.h>
 #include <private/qqmldirparser_p.h>
+#include <private/qqmlbundle_p.h>
+#include <private/qflagpointer_p.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -117,6 +120,25 @@ public:
 
     QList<QQmlError> errors() const;
 
+    class Data {
+    public:
+        inline const char *data() const;
+        inline int size() const;
+
+        inline QByteArray asByteArray() const;
+
+        inline bool isFile() const;
+        inline QQmlFile *asFile() const;
+
+    private:
+        friend class QQmlDataBlob;
+        friend class QQmlDataLoader;
+        inline Data();
+        Data(const Data &);
+        Data &operator=(const Data &);
+        QBiPointer<const QByteArray, QQmlFile> d;
+    };
+
 protected:
     // Can be called from within callbacks
     void setError(const QQmlError &);
@@ -124,7 +146,7 @@ protected:
     void addDependency(QQmlDataBlob *);
 
     // Callbacks made in load thread
-    virtual void dataReceived(const QByteArray &) = 0;
+    virtual void dataReceived(const Data &) = 0;
     virtual void done();
     virtual void networkError(QNetworkReply::NetworkError);
     virtual void dependencyError(QQmlDataBlob *);
@@ -216,12 +238,22 @@ private:
     typedef QHash<QNetworkReply *, QQmlDataBlob *> NetworkReplies;
 
     void setData(QQmlDataBlob *, const QByteArray &);
+    void setData(QQmlDataBlob *, QQmlFile *);
+    void setData(QQmlDataBlob *, const QQmlDataBlob::Data &);
 
     QQmlEngine *m_engine;
     QQmlDataLoaderThread *m_thread;
     NetworkReplies m_networkReplies;
 };
 
+class QQmlBundleData : public QQmlBundle,
+                       public QQmlRefCount
+{
+public:
+    QQmlBundleData(const QString &);
+    QString fileName;
+};
+
 // Exported for QtQuick1
 class Q_QML_PRIVATE_EXPORT QQmlTypeLoader : public QQmlDataLoader
 {
@@ -243,22 +275,36 @@ public:
     QQmlScriptBlob *getScript(const QUrl &);
     QQmlQmldirData *getQmldir(const QUrl &);
 
+    QQmlBundleData *getBundle(const QString &);
+    QQmlBundleData *getBundle(const QHashedStringRef &);
+    void addBundle(const QString &, const QString &);
+
     QString absoluteFilePath(const QString &path);
     bool directoryExists(const QString &path);
-    const QQmlDirParser *qmlDirParser(const QString &absoluteFilePath);
+    const QQmlDirParser *qmlDirParser(const QString &filePath, const QString &uriHint, QString *outUrl);
+
 private:
+    void addBundleNoLock(const QString &, const QString &);
+    QString bundleIdForQmldir(const QString &qmldir, const QString &uriHint);
+
+    struct DirParser : public QQmlDirParser { QString adjustedUrl; };
+
     typedef QHash<QUrl, QQmlTypeData *> TypeCache;
     typedef QHash<QUrl, QQmlScriptBlob *> ScriptCache;
     typedef QHash<QUrl, QQmlQmldirData *> QmldirCache;
     typedef QStringHash<bool> StringSet;
     typedef QStringHash<StringSet*> ImportDirCache;
-    typedef QStringHash<QQmlDirParser*> ImportQmlDirCache;
+    typedef QStringHash<DirParser*> ImportQmlDirCache;
+    typedef QStringHash<QQmlBundleData *> BundleCache;
+    typedef QStringHash<QString> QmldirBundleIdCache;
 
     TypeCache m_typeCache;
     ScriptCache m_scriptCache;
     QmldirCache m_qmldirCache;
     ImportDirCache m_importDirCache;
     ImportQmlDirCache m_importQmlDirCache;
+    BundleCache m_bundleCache;
+    QmldirBundleIdCache m_qmldirBundleIdCache;
 };
 
 Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlTypeLoader::Options)
@@ -312,7 +358,7 @@ public:
 protected:
     virtual void done();
     virtual void completed();
-    virtual void dataReceived(const QByteArray &);
+    virtual void dataReceived(const Data &);
     virtual void allDependenciesDone();
     virtual void downloadProgressChanged(qreal);
 
@@ -349,8 +395,7 @@ private:
 // reference that was created is released but final deletion only occurs once all the
 // references as released.  This is all intended to ensure that the v8 resources are
 // only created and destroyed in the main thread :)
-class Q_AUTOTEST_EXPORT QQmlScriptData : public QQmlCleanup, 
-                                                 public QQmlRefCount
+class Q_AUTOTEST_EXPORT QQmlScriptData : public QQmlCleanup, public QQmlRefCount
 {
 public:
     QQmlScriptData();
@@ -402,7 +447,7 @@ public:
     QQmlScriptData *scriptData() const;
 
 protected:
-    virtual void dataReceived(const QByteArray &);
+    virtual void dataReceived(const Data &);
     virtual void done();
 
 private:
@@ -424,13 +469,52 @@ public:
     const QQmlDirComponents &dirComponents() const;
 
 protected:
-    virtual void dataReceived(const QByteArray &);
+    virtual void dataReceived(const Data &);
 
 private:
     QQmlDirComponents m_components;
 
 };
 
+QQmlDataBlob::Data::Data()
+{
+}
+
+const char *QQmlDataBlob::Data::data() const
+{
+    Q_ASSERT(!d.isNull());
+
+    if (d.isT1()) return d.asT1()->constData();
+    else return d.asT2()->data();
+}
+
+int QQmlDataBlob::Data::size() const
+{
+    Q_ASSERT(!d.isNull());
+
+    if (d.isT1()) return d.asT1()->size();
+    else return d.asT2()->size();
+}
+
+bool QQmlDataBlob::Data::isFile() const
+{
+    return d.isT2();
+}
+
+QByteArray QQmlDataBlob::Data::asByteArray() const
+{
+    Q_ASSERT(!d.isNull());
+
+    if (d.isT1()) return *d.asT1();
+    else return d.asT2()->dataByteArray();
+}
+
+QQmlFile *QQmlDataBlob::Data::asFile() const
+{
+    if (d.isT2()) return d.asT2();
+    else return 0;
+}
+
 QT_END_NAMESPACE
 
 #endif // QQMLTYPELOADER_P_H
index b00847a..b0d39fd 100644 (file)
@@ -55,6 +55,7 @@
 #include <QtCore/qdatetime.h>
 #include <QtNetwork/qnetworkaccessmanager.h>
 #include <QtQml/qqmlinfo.h>
+#include <QtQml/qqmlfile.h>
 #include "qqmlnetworkaccessmanagerfactory.h"
 
 #include <private/qv8engine_p.h>
@@ -362,7 +363,7 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
     if (url.isRelative())
         return;
 
-    QString fileName = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
+    QString fileName = QQmlFile::urlToLocalFileOrQrc(url);
 
     QFile f(fileName);
     if (f.open(QIODevice::ReadOnly)) {
index 89f60f2..01a670a 100644 (file)
@@ -45,6 +45,7 @@
 #include <QtNetwork/qnetworkrequest.h>
 #include <QtNetwork/qnetworkreply.h>
 #include <QtCore/qfile.h>
+#include <QtQml/qqmlfile.h>
 
 #include <private/qqmlengine_p.h>
 
@@ -185,7 +186,7 @@ v8::Handle<v8::Value> QV8Include::include(const v8::Arguments &args)
     if (args.Length() >= 2 && args[1]->IsFunction())
         callbackFunction = v8::Local<v8::Function>::Cast(args[1]);
 
-    QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
+    QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
 
     v8::Local<v8::Object> result;
 
index 9319cf1..1d4adf9 100644 (file)
 #ifndef QT_NO_MOVIE
 
 #include <QtQml/qqmlinfo.h>
+#include <QtQml/qqmlfile.h>
+#include <QtQml/qqmlengine.h>
 #include <QtGui/qmovie.h>
 #include <QtNetwork/qnetworkrequest.h>
 #include <QtNetwork/qnetworkreply.h>
 
-#include <private/qqmlengine_p.h>
-
 QT_BEGIN_NAMESPACE
 /*!
     \qmlclass AnimatedImage QQuickAnimatedImage
@@ -264,7 +264,7 @@ void QQuickAnimatedImage::load()
         if (d->progress != oldProgress)
             emit progressChanged(d->progress);
     } else {
-        QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(d->url);
+        QString lf = QQmlFile::urlToLocalFileOrQrc(d->url);
         if (!lf.isEmpty()) {
             //### should be unified with movieRequestFinished
             d->_movie = new QMovie(lf);
index b99a2f4..67ae3fa 100644 (file)
 #include "qquickninepatchnode_p.h"
 
 #include <QtQml/qqmlinfo.h>
+#include <QtQml/qqmlfile.h>
+#include <QtQml/qqmlengine.h>
+#include <QtNetwork/qnetworkreply.h>
 #include <QtCore/qfile.h>
 
 #include <private/qqmlglobal_p.h>
-#include <private/qqmlengine_p.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -306,7 +308,7 @@ void QQuickBorderImage::load()
     } else {
         d->status = Loading;
         if (d->url.path().endsWith(QLatin1String("sci"))) {
-            QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(d->url);
+            QString lf = QQmlFile::urlToLocalFileOrQrc(d->url);
             if (!lf.isEmpty()) {
                 QFile file(lf);
                 file.open(QIODevice::ReadOnly);
index 55830f0..2868270 100644 (file)
@@ -52,8 +52,8 @@
 #include <QFontDatabase>
 
 #include <private/qobject_p.h>
-#include <private/qqmlengine_p.h>
 #include <qqmlinfo.h>
+#include <qqmlfile.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -196,7 +196,7 @@ void QQuickFontLoader::setSource(const QUrl &url)
     d->url = url;
     emit sourceChanged();
 
-    QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(d->url);
+    QString localFile = QQmlFile::urlToLocalFileOrQrc(d->url);
     if (!localFile.isEmpty()) {
         if (!d->fonts.contains(d->url)) {
             int id = QFontDatabase::addApplicationFont(localFile);
index 801b007..0400c95 100644 (file)
@@ -65,6 +65,7 @@
 #include <QtCore/qdebug.h>
 #include <private/qobject_p.h>
 #include <QSslError>
+#include <QQmlFile>
 
 #define IMAGEREQUEST_MAX_REQUEST_COUNT       8
 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
@@ -556,7 +557,7 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
         }
 
     } else {
-        QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
+        QString lf = QQmlFile::urlToLocalFileOrQrc(url);
         if (!lf.isEmpty()) {
             // Image is local - load/decode immediately
             QImage image;
@@ -975,7 +976,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
             QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
     }
 
-    QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
+    QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
     if (localFile.isEmpty()) 
         return 0;
 
index 6f226a8..d99d18c 100644 (file)
@@ -47,6 +47,7 @@ PRIVATETESTS += \
     qquicklistmodel \
     qquicklistmodelworkerscript \
     qquickworkerscript \
+    qqmlbundle \
     v4
 
 SUBDIRS += $$PUBLICTESTS
diff --git a/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.1.qml b/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.1.qml
new file mode 100644 (file)
index 0000000..b87ba9c
--- /dev/null
@@ -0,0 +1,4 @@
+import "bundle://mybundle"
+
+MyType {
+}
diff --git a/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.2.qml b/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.2.qml
new file mode 100644 (file)
index 0000000..0c0622e
--- /dev/null
@@ -0,0 +1,4 @@
+import "bundle://mybundle/subdir"
+
+MySubType {
+}
diff --git a/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/MyType.qml b/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/MyType.qml
new file mode 100644 (file)
index 0000000..c473560
--- /dev/null
@@ -0,0 +1,6 @@
+import QtQuick 2.0
+
+QtObject {
+    property real test1: 1918
+    property string test2: "Hello world!"
+}
diff --git a/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/subdir/MySubType.qml b/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/subdir/MySubType.qml
new file mode 100644 (file)
index 0000000..ce136f2
--- /dev/null
@@ -0,0 +1,7 @@
+import QtQuick 2.0
+
+QtObject {
+    property real test1: 1432
+    property string test2: "Jeronimo"
+}
+
diff --git a/tests/auto/qml/qqmlbundle/data/componentFromBundle/bundledata/test.qml b/tests/auto/qml/qqmlbundle/data/componentFromBundle/bundledata/test.qml
new file mode 100644 (file)
index 0000000..ce81efe
--- /dev/null
@@ -0,0 +1,6 @@
+import QtQuick 2.0
+
+QtObject {
+    property int test1: 11
+    property bool test2: true
+}
diff --git a/tests/auto/qml/qqmlbundle/data/import.qml b/tests/auto/qml/qqmlbundle/data/import.qml
new file mode 100644 (file)
index 0000000..af52719
--- /dev/null
@@ -0,0 +1,4 @@
+import bundletest 2.0
+
+MyPluginType {
+}
diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/qmldir b/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/qmldir
new file mode 100644 (file)
index 0000000..db8aabf
--- /dev/null
@@ -0,0 +1 @@
+plugin plugin1
diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/subdir/qmldir b/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/subdir/qmldir
new file mode 100644 (file)
index 0000000..305d075
--- /dev/null
@@ -0,0 +1 @@
+plugin plugin2
diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/empty.json b/tests/auto/qml/qqmlbundle/data/imports/bundletest/empty.json
new file mode 100644 (file)
index 0000000..0967ef4
--- /dev/null
@@ -0,0 +1 @@
+{}
diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin.cpp b/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin.cpp
new file mode 100644 (file)
index 0000000..8f94e7e
--- /dev/null
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QStringList>
+#include <QtQml/qqmlextensionplugin.h>
+#include <QtQml/qqml.h>
+#include <QDebug>
+
+class MyPluginType : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(int value READ value WRITE setValue)
+
+public:
+    MyPluginType(QObject *parent=0) : QObject(parent), v(32)
+    {
+    }
+
+    int value() const { return v; }
+    void setValue(int i) { v = i; }
+
+private:
+    int v;
+};
+
+
+class MyPlugin : public QQmlExtensionPlugin
+{
+    Q_OBJECT
+    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface" FILE "empty.json")
+
+public:
+    MyPlugin()
+    {
+    }
+
+    void registerTypes(const char *uri)
+    {
+        qmlRegisterType<MyPluginType>(uri, 2, 0, "MyPluginType");
+    }
+};
+
+#include "plugin.moc"
+
diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin1.pro b/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin1.pro
new file mode 100644 (file)
index 0000000..d91cc24
--- /dev/null
@@ -0,0 +1,5 @@
+TEMPLATE = lib
+CONFIG += plugin
+SOURCES += plugin.cpp
+QT = core qml
+
diff --git a/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/qmldir b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/qmldir
new file mode 100644 (file)
index 0000000..628edcb
--- /dev/null
@@ -0,0 +1 @@
+MySubdirType 1.0 st.qml
diff --git a/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/st.qml b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/st.qml
new file mode 100644 (file)
index 0000000..496eda8
--- /dev/null
@@ -0,0 +1,6 @@
+import QtQuick 2.0
+
+QtObject {
+    property int test1: 67
+    property real test2: 88
+}
diff --git a/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/test.qml b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/test.qml
new file mode 100644 (file)
index 0000000..1046b88
--- /dev/null
@@ -0,0 +1,4 @@
+import "subdir"
+
+MySubdirType {
+}
diff --git a/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/MyType.qml b/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/MyType.qml
new file mode 100644 (file)
index 0000000..ce81efe
--- /dev/null
@@ -0,0 +1,6 @@
+import QtQuick 2.0
+
+QtObject {
+    property int test1: 11
+    property bool test2: true
+}
diff --git a/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/test.qml b/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/test.qml
new file mode 100644 (file)
index 0000000..2e753d2
--- /dev/null
@@ -0,0 +1,4 @@
+import QtQuick 2.0
+
+MyType {
+}
diff --git a/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/MyType.qml b/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/MyType.qml
new file mode 100644 (file)
index 0000000..ce81efe
--- /dev/null
@@ -0,0 +1,6 @@
+import QtQuick 2.0
+
+QtObject {
+    property int test1: 11
+    property bool test2: true
+}
diff --git a/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/test.qml b/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/test.qml
new file mode 100644 (file)
index 0000000..2e753d2
--- /dev/null
@@ -0,0 +1,4 @@
+import QtQuick 2.0
+
+MyType {
+}
diff --git a/tests/auto/qml/qqmlbundle/qqmlbundle.pro b/tests/auto/qml/qqmlbundle/qqmlbundle.pro
new file mode 100644 (file)
index 0000000..ec81e3f
--- /dev/null
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS += tst_qqmlbundle.pro data/imports/bundletest/plugin1.pro
diff --git a/tests/auto/qml/qqmlbundle/tst_qqmlbundle.cpp b/tests/auto/qml/qqmlbundle/tst_qqmlbundle.cpp
new file mode 100644 (file)
index 0000000..fee01c2
--- /dev/null
@@ -0,0 +1,260 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// Lookup of libraries
+// Test bundle as a qmldir
+
+#include <qtest.h>
+#include <QDebug>
+#include <QQmlEngine>
+#include <QQmlComponent>
+#include "../../shared/util.h"
+#include <private/qqmlbundle_p.h>
+
+class tst_qqmlbundle : public QQmlDataTest
+{
+    Q_OBJECT
+public:
+    tst_qqmlbundle() {}
+
+private slots:
+    void initTestCase();
+
+    void componentFromBundle();
+    void relativeResolution();
+    void bundleImport();
+    void relativeQmldir();
+
+    void import();
+
+private:
+    QStringList findFiles(const QDir &d);
+    bool makeBundle(const QString &path, const QString &name);
+};
+
+void tst_qqmlbundle::initTestCase()
+{
+    QQmlDataTest::initTestCase();
+}
+
+// Test we create a QQmlComponent for a file inside a bundle
+void tst_qqmlbundle::componentFromBundle()
+{
+    QVERIFY(makeBundle(testFile("componentFromBundle"), "my.bundle"));
+
+    QQmlEngine engine;
+    engine.addNamedBundle("mybundle", testFile("componentFromBundle/my.bundle"));
+
+    QQmlComponent component(&engine, QUrl("bundle://mybundle/test.qml"));
+    QVERIFY(component.isReady());
+
+    QObject *o = component.create();
+    QVERIFY(o != 0);
+
+    QCOMPARE(o->property("test1").toInt(), 11);
+    QCOMPARE(o->property("test2").toBool(), true);
+
+    delete o;
+}
+
+// Tests that relative QML components are resolved without a qmldir
+void tst_qqmlbundle::relativeResolution()
+{
+    // Root of the bundle
+    {
+    QVERIFY(makeBundle(testFile("relativeResolution.1"), "my.bundle"));
+
+    QQmlEngine engine;
+    engine.addNamedBundle("mybundle", testFile("relativeResolution.1/my.bundle"));
+
+    QQmlComponent component(&engine, QUrl("bundle://mybundle/test.qml"));
+    QVERIFY(component.isReady());
+
+    QObject *o = component.create();
+    QVERIFY(o != 0);
+
+    QCOMPARE(o->property("test1").toInt(), 11);
+    QCOMPARE(o->property("test2").toBool(), true);
+
+    delete o;
+    }
+
+    // Non-root of the bundle
+    {
+    QVERIFY(makeBundle(testFile("relativeResolution.2"), "my.bundle"));
+
+    QQmlEngine engine;
+    engine.addNamedBundle("mybundle", testFile("relativeResolution.2/my.bundle"));
+
+    QQmlComponent component(&engine, QUrl("bundle://mybundle/subdir/test.qml"));
+    QVERIFY(component.isReady());
+
+    QObject *o = component.create();
+    QVERIFY(o != 0);
+
+    QCOMPARE(o->property("test1").toInt(), 11);
+    QCOMPARE(o->property("test2").toBool(), true);
+
+    delete o;
+    }
+}
+
+// Test that a bundle can be imported explicitly from outside a bundle
+void tst_qqmlbundle::bundleImport()
+{
+    QVERIFY(makeBundle(testFile("bundleImport"), "my.bundle"));
+
+    QQmlEngine engine;
+    engine.addNamedBundle("mybundle", testFile("bundleImport/my.bundle"));
+
+    {
+    QQmlComponent component(&engine, testFileUrl("bundleImport/bundleImport.1.qml"));
+    QVERIFY(component.isReady());
+
+    QObject *o = component.create();
+    QVERIFY(o != 0);
+
+    QCOMPARE(o->property("test1").toReal(), qreal(1918));
+    QCOMPARE(o->property("test2").toString(), QString("Hello world!"));
+
+    delete o;
+    }
+
+    {
+    QQmlComponent component(&engine, testFileUrl("bundleImport/bundleImport.2.qml"));
+    QVERIFY(component.isReady());
+
+    QObject *o = component.create();
+    QVERIFY(o != 0);
+
+    QCOMPARE(o->property("test1").toReal(), qreal(1432));
+    QCOMPARE(o->property("test2").toString(), QString("Jeronimo"));
+
+    delete o;
+    }
+}
+
+// Test a relative import inside a bundle uses qmldir
+void tst_qqmlbundle::relativeQmldir()
+{
+    QVERIFY(makeBundle(testFile("relativeQmldir"), "my.bundle"));
+
+    QQmlEngine engine;
+    engine.addNamedBundle("mybundle", testFile("relativeQmldir/my.bundle"));
+
+    QQmlComponent component(&engine, QUrl("bundle://mybundle/test.qml"));
+    QVERIFY(component.isReady());
+
+    QObject *o = component.create();
+    QVERIFY(o != 0);
+
+    QCOMPARE(o->property("test1").toReal(), qreal(67));
+    QCOMPARE(o->property("test2").toReal(), qreal(88));
+
+    delete o;
+}
+
+// Test C++ plugins are resolved relative to the bundle container file
+void tst_qqmlbundle::import()
+{
+    QVERIFY(makeBundle(testFile("imports/bundletest"), "qmldir"));
+
+    QQmlEngine engine;
+    engine.addImportPath(testFile("imports"));
+
+    QQmlComponent component(&engine, testFileUrl("import.qml"));
+    QVERIFY(component.isReady());
+
+    QObject *o = component.create();
+    QVERIFY(o != 0);
+
+    QCOMPARE(o->property("value").toInt(), 32);
+
+    delete o;
+}
+
+// Transform the data available under <path>/bundledata to a bundle named <path>/<name>
+bool tst_qqmlbundle::makeBundle(const QString &path, const QString &name)
+{
+    QDir dir(path);
+    dir.remove(name);
+
+    QDir bundleDir = dir;
+    if (!bundleDir.cd("bundledata"))
+        return false;
+
+    QStringList fileNames = findFiles(bundleDir);
+
+    QString bundleFile = dir.absolutePath() + QDir::separator() + name;
+
+    QQmlBundle bundle(bundleFile);
+    if (!bundle.open(QFile::WriteOnly))
+        return false;
+
+    foreach (const QString &fileName, fileNames) {
+        QString shortFileName = fileName.mid(bundleDir.absolutePath().length() + 1);
+        bundle.add(shortFileName, fileName);
+    }
+
+    return true;
+}
+
+QStringList tst_qqmlbundle::findFiles(const QDir &d)
+{
+    QStringList rv;
+
+    QStringList files = d.entryList(QDir::Files);
+    foreach (const QString &file, files)
+        rv << d.absoluteFilePath(file);
+
+    QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
+    foreach (const QString &dir, dirs) {
+        QDir sub = d;
+        sub.cd(dir);
+        rv << findFiles(sub);
+    }
+
+    return rv;
+}
+
+QTEST_MAIN(tst_qqmlbundle)
+
+#include "tst_qqmlbundle.moc"
diff --git a/tests/auto/qml/qqmlbundle/tst_qqmlbundle.pro b/tests/auto/qml/qqmlbundle/tst_qqmlbundle.pro
new file mode 100644 (file)
index 0000000..4e9c627
--- /dev/null
@@ -0,0 +1,15 @@
+CONFIG += testcase
+TARGET = tst_qqmlbundle
+macx:CONFIG -= app_bundle
+
+SOURCES += tst_qqmlbundle.cpp
+HEADERS +=
+
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+CONFIG += parallel_test
+
+QT += qml-private testlib
+
diff --git a/tools/qmlbundle/main.cpp b/tools/qmlbundle/main.cpp
new file mode 100644 (file)
index 0000000..16bf20e
--- /dev/null
@@ -0,0 +1,220 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qqmlbundle_p.h>
+#include <private/qqmlscript_p.h>
+#include <QtCore/QtCore>
+#include <iostream>
+
+static bool createBundle(const QString &fileName, const QStringList &fileNames)
+{
+    QQmlBundle bundle(fileName);
+    if (!bundle.open(QFile::WriteOnly))
+        return false;
+    foreach (const QString &fileName, fileNames)
+        bundle.add(fileName);
+    return true;
+}
+
+static bool removeFiles(const QString &fileName, const QStringList &fileNames)
+{
+    const QSet<QString> filesToRemove = QSet<QString>::fromList(fileNames);
+
+    QQmlBundle bundle(fileName);
+    bundle.open(QFile::ReadWrite);
+    foreach (const QQmlBundle::FileEntry *entry, bundle.files()) {
+        if (filesToRemove.contains(entry->fileName()))
+            bundle.remove(entry);
+    }
+    return true;
+}
+
+static void showHelp()
+{
+    std::cerr << "Usage: qmlbundle <command> [<args>]" << std::endl
+              << std::endl
+              << "The commands are:" << std::endl
+              << "  create     Create a new bundle" << std::endl
+              << "  add        Add files to the bundle" << std::endl
+              << "  rm         Remove files from the bundle" << std::endl
+              << "  update     Add files to the bundle or update them if they are already added" << std::endl
+              << "  ls         List the files in the bundle" << std::endl
+              << "  cat        Concatenates files and print on the standard output" << std::endl
+              << "  optimize   Insert optimization data for all recognised content" << std::endl
+              << std::endl
+              << "See 'qmlbundle help <command>' for more information on a specific command." << std::endl;
+}
+
+static void usage(const QString &action, const QString &error = QString())
+{
+    if (! error.isEmpty())
+        std::cerr << qPrintable(error) << std::endl << std::endl;
+
+    if (action == QLatin1String("create")) {
+        std::cerr << "usage: qmlbundle create <bundle name> [files]" << std::endl;
+    } else if (action == QLatin1String("add")) {
+        std::cerr << "usage: qmlbundle add <bundle name> [files]" << std::endl;
+    } else if (action == QLatin1String("rm")) {
+        std::cerr << "usage: qmlbundle rm <bundle name> [files]" << std::endl;
+    } else if (action == QLatin1String("update")) {
+        std::cerr << "usage: qmlbundle update <bundle name> [files]" << std::endl;
+    } else if (action == QLatin1String("ls")) {
+        std::cerr << "usage: qmlbundle ls <bundle name>" << std::endl;
+    } else if (action == QLatin1String("cat")) {
+        std::cerr << "usage: qmlbundle cat <bundle name> [files]" << std::endl;
+    } else {
+        showHelp();
+    }
+}
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+
+    QStringList args = app.arguments();
+    /*const QString exeName =*/ args.takeFirst();
+
+    if (args.isEmpty()) {
+        showHelp();
+        return 0;
+    }
+
+    const QString action = args.takeFirst();
+
+    if (action == QLatin1String("help")) {
+        if (args.empty())
+            showHelp();
+        else
+            usage(args.takeFirst());
+    } else if (action == QLatin1String("ls")) {
+        if (args.isEmpty()) {
+            usage(action, "You must specify a bundle");
+            return EXIT_FAILURE;
+        }
+        QQmlBundle bundle(args.takeFirst());
+        if (bundle.open(QFile::ReadOnly)) {
+            foreach (const QQmlBundle::FileEntry *fileEntry, bundle.files())
+                std::cout << qPrintable(fileEntry->fileName()) << std::endl;
+        }
+    } else if (action == QLatin1String("create")) {
+        if (args.isEmpty()) {
+            usage(action, "You must specify a bundle");
+            return EXIT_FAILURE;
+        }
+        const QString bundleFileName = args.takeFirst();
+        createBundle(bundleFileName, args);
+    } else if (action == QLatin1String("add")) {
+        if (args.isEmpty()) {
+            usage(action, "You must specify a bundle");
+            return EXIT_FAILURE;
+        }
+        const QString bundleFileName = args.takeFirst();
+        QQmlBundle bundle(bundleFileName);
+        bundle.open();
+        foreach (const QString &fileName, args) {
+            if (! bundle.add(fileName))
+                std::cerr << "cannot add file " << qPrintable(fileName) << " to " << qPrintable(bundleFileName) << std::endl;
+        }
+    } else if (action == QLatin1String("rm")) {
+        if (args.isEmpty()) {
+            usage(action, "You must specify a bundle");
+            return EXIT_FAILURE;
+        }
+        const QString bundleFileName = args.takeFirst();
+        removeFiles(bundleFileName, args);
+    } else if (action == QLatin1String("update")) {
+        if (args.isEmpty()) {
+            usage(action, "You must specify a bundle");
+            return EXIT_FAILURE;
+        }
+        const QString bundleFileName = args.takeFirst();
+        removeFiles(bundleFileName, args);
+        QQmlBundle bundle(bundleFileName);
+        bundle.open();
+        foreach (const QString &fileName, args) {
+            if (! bundle.add(fileName))
+                std::cerr << "cannot add file " << qPrintable(fileName) << " to " << qPrintable(bundleFileName) << std::endl;
+        }
+    } else if (action == QLatin1String("cat")) {
+        if (args.isEmpty()) {
+            usage(action, "You must specify a bundle");
+            return EXIT_FAILURE;
+        }
+        const QString bundleFileName = args.takeFirst();
+        QQmlBundle bundle(bundleFileName);
+        if (bundle.open(QFile::ReadOnly)) {
+            const QSet<QString> filesToShow = QSet<QString>::fromList(args);
+
+            foreach (const QQmlBundle::FileEntry *fileEntry, bundle.files()) {
+                if (filesToShow.contains(fileEntry->fileName()))
+                    std::cout.write(fileEntry->contents(), fileEntry->fileSize());
+            }
+        }
+    } else if (action == QLatin1String("optimize")) {
+        if (args.isEmpty()) {
+            usage(action, "You must specify a bundle");
+            return EXIT_FAILURE;
+        }
+        const QString bundleFileName = args.takeFirst();
+        QQmlBundle bundle(bundleFileName);
+        if (bundle.open(QFile::ReadWrite)) {
+            QList<const QQmlBundle::FileEntry *> files = bundle.files();
+            for (int ii = 0; ii < files.count(); ++ii) {
+                const QQmlBundle::FileEntry *file = files.at(ii);
+
+                if (!file->fileName().endsWith(".qml"))
+                    continue;
+
+                QQmlScript::Parser parser;
+                QString data = QString::fromUtf8(file->contents(), file->fileSize());
+                parser.parse(data, QByteArray());
+                QByteArray preparse = parser.preparseData();
+
+                if (!preparse.isEmpty())
+                    bundle.addMetaLink(file->fileName(), QLatin1String("qml:preparse"), preparse);
+            }
+        }
+    } else {
+        showHelp();
+    }
+
+    return 0;
+}
diff --git a/tools/qmlbundle/qmlbundle.pro b/tools/qmlbundle/qmlbundle.pro
new file mode 100644 (file)
index 0000000..6a89418
--- /dev/null
@@ -0,0 +1,12 @@
+TEMPLATE = app
+TARGET = qmlbundle
+DESTDIR= $$QT.qml.bins
+
+QT       = core qml-private v8-private core-private
+CONFIG  += console
+CONFIG  -= app_bundle
+
+SOURCES += main.cpp
+
+target.path = $$[QT_INSTALL_BINS]
+INSTALLS += target
index 32c9be7..0d97ba1 100644 (file)
@@ -350,6 +350,8 @@ static void usage()
     qWarning("  --no-version-detection .................... Do not try to detect the version of the .qml file");
     qWarning("  --slow-animations ......................... Run all animations in slow motion");
     qWarning("  --quit .................................... Quit immediately after starting");
+    qWarning("  -I <path> ................................. Add <path> to the list of import paths");
+    qWarning("  -B <name> <file> .......................... Add a named bundle");
 
     qWarning(" ");
     exit(1);
@@ -360,6 +362,7 @@ int main(int argc, char ** argv)
     Options options;
 
     QStringList imports;
+    QList<QPair<QString, QString> > bundles;
     for (int i = 1; i < argc; ++i) {
         if (*argv[i] != '-' && QFileInfo(QFile::decodeName(argv[i])).exists()) {
             options.file = QUrl::fromLocalFile(argv[i]);
@@ -379,7 +382,11 @@ int main(int argc, char ** argv)
                 options.quitImmediately = true;
             else if (lowerArgument == QLatin1String("-i") && i + 1 < argc)
                 imports.append(QString::fromLatin1(argv[++i]));
-            else if (lowerArgument == QLatin1String("--help")
+            else if (lowerArgument == QLatin1String("-b") && i + 2 < argc) {
+                QString name = QString::fromLatin1(argv[++i]);
+                QString file = QString::fromLatin1(argv[++i]);
+                bundles.append(qMakePair(name, file));
+            } else if (lowerArgument == QLatin1String("--help")
                      || lowerArgument == QLatin1String("-help")
                      || lowerArgument == QLatin1String("--h")
                      || lowerArgument == QLatin1String("-h"))
@@ -416,6 +423,8 @@ int main(int argc, char ** argv)
             engine = qxView->engine();
             for (int i = 0; i < imports.size(); ++i)
                 engine->addImportPath(imports.at(i));
+            for (int i = 0; i < bundles.size(); ++i)
+                engine->addNamedBundle(bundles.at(i).first, bundles.at(i).second);
             window = qxView;
             if (options.file.isLocalFile()) {
                 QFileInfo fi(options.file.toLocalFile());
index 70aebf4..1ce5667 100644 (file)
@@ -1,5 +1,5 @@
 TEMPLATE = subdirs
-SUBDIRS +=  qmlscene qmlplugindump qmlmin qmleasing qmlprofiler
+SUBDIRS +=  qmlscene qmlplugindump qmlmin qmleasing qmlprofiler qmlbundle
 !contains(QT_CONFIG, no-widgets):SUBDIRS += easingcurveeditor
 contains(QT_CONFIG, qmltest): SUBDIRS += qmltestrunner